<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yukihiro amadatsu</title>
    <description>The latest articles on DEV Community by yukihiro amadatsu (@suruseas).</description>
    <link>https://dev.to/suruseas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F852203%2Fe8e2c8e4-a9f6-4726-9878-22f90505518c.jpeg</url>
      <title>DEV Community: yukihiro amadatsu</title>
      <link>https://dev.to/suruseas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/suruseas"/>
    <language>en</language>
    <item>
      <title>I ran 156 real-world docker compose projects on Apple's container. The failures fell into patterns a machine can fix</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Tue, 28 Jul 2026 14:03:35 +0000</pubDate>
      <link>https://dev.to/suruseas/i-ran-156-real-world-docker-compose-projects-on-apples-container-the-failures-fell-into-patterns-50dh</link>
      <guid>https://dev.to/suruseas/i-ran-156-real-world-docker-compose-projects-on-apples-container-the-failures-fell-into-patterns-50dh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the English version of &lt;a href="https://zenn.dev/suruseas/articles/786ae0a8866501" rel="noopener noreferrer"&gt;my Japanese post on Zenn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Back in July I &lt;a href="https://dev.to/suruseas/apples-container-hit-10-should-it-be-your-dev-environment-yet-i-measured-1e00"&gt;measured Apple's &lt;code&gt;container&lt;/code&gt; runtime&lt;/a&gt; against 29 samples from Docker's official &lt;a href="https://github.com/docker/awesome-compose" rel="noopener noreferrer"&gt;awesome-compose&lt;/a&gt; collection and concluded: not your main dev stack yet.&lt;/p&gt;

&lt;p&gt;Something kept bugging me about that verdict, though. Official samples are polite. Real-world compose files are not: secrets live outside the file, services join a pre-existing reverse-proxy network, data sits in bind mounts under &lt;code&gt;/mnt/docker-volumes&lt;/code&gt; because that's how the self-hosting community rolls. If you want the real compatibility picture, you have to feed the runtime the messy stuff.&lt;/p&gt;

&lt;p&gt;So this time I fed it everything: all 161 entries of &lt;a href="https://github.com/Haxxnet/Compose-Examples" rel="noopener noreferrer"&gt;Haxxnet/Compose-Examples&lt;/a&gt;, the self-hosting community's staple collection. This post is the report — the counts, a taxonomy of why things fail, and what happened when I taught a tool to fix the mechanical failures automatically: full-stack startup went from 39% to 50%.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Corpus: 161 entries, of which 159 have a compose file&lt;/li&gt;
&lt;li&gt;Parse/validate: all 159&lt;/li&gt;
&lt;li&gt;Startup test: the 156 without a &lt;code&gt;build:&lt;/code&gt; section (image-only), each started, classified, and torn down by a script&lt;/li&gt;
&lt;li&gt;Two arms per project, back to back, on the same binary: &lt;strong&gt;A&lt;/strong&gt; = plain &lt;code&gt;up&lt;/code&gt;, &lt;strong&gt;B&lt;/strong&gt; = &lt;code&gt;up --from-docker-compose&lt;/code&gt; (auto-fix on — more on that below)&lt;/li&gt;
&lt;li&gt;Secrets and passwords left as placeholders. This is the deliberately hostile "run &lt;code&gt;up&lt;/code&gt; without reading the README" condition&lt;/li&gt;
&lt;li&gt;120-second hard timeout per &lt;code&gt;up&lt;/code&gt; (this timeout misclassifies exactly one project later — I'll get to it)&lt;/li&gt;
&lt;li&gt;Environment: macOS 26 / Apple silicon, &lt;code&gt;container&lt;/code&gt; 1.1.0, with &lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;opossum&lt;/a&gt; v0.15.0 as the orchestration layer — a thin translator from compose to &lt;code&gt;container&lt;/code&gt; CLI calls, so this sweep doubles as its compatibility test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reproducing it is essentially this (drop &lt;code&gt;--from-docker-compose&lt;/code&gt; for the no-fix arm):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;container system start                    &lt;span class="c"&gt;# once&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;container system dns create opossum  &lt;span class="c"&gt;# once (name resolution)&lt;/span&gt;
opossum up &lt;span class="nt"&gt;--from-docker-compose&lt;/span&gt;          &lt;span class="c"&gt;# in each compose directory&lt;/span&gt;
opossum ps                                &lt;span class="c"&gt;# classification comes from this output&lt;/span&gt;
opossum down &lt;span class="nt"&gt;-v&lt;/span&gt;                           &lt;span class="c"&gt;# cleanup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each run lands in one of six classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FULL-UP&lt;/td&gt;
&lt;td&gt;every service running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PARTIAL&lt;/td&gt;
&lt;td&gt;some services running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRASH&lt;/td&gt;
&lt;td&gt;a container crashed right after start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STARTED-NONERUN&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;up&lt;/code&gt; finished within 120s but nothing was running (instant exits, image pull failures — mixed, not individually audited)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PREFLIGHT&lt;/td&gt;
&lt;td&gt;stopped before starting (host port taken, missing external network, …)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIMEOUT&lt;/td&gt;
&lt;td&gt;cut off by the 120s timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The raw numbers first
&lt;/h2&gt;

&lt;p&gt;Parsing: all 159 files load. Zero parse errors, zero unreadable files. Real-world compose files always contain fields Apple's runtime has no counterpart for — &lt;code&gt;container_name&lt;/code&gt; (149 of them), &lt;code&gt;restart&lt;/code&gt; (143) — so "ignore unsupported fields and list them in a warning" turns out to be exactly the policy that makes the whole corpus loadable.&lt;/p&gt;

&lt;p&gt;The mass startup, no modifications (arm A):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FULL-UP&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PARTIAL&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRASH&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STARTED-NONERUN&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PREFLIGHT&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIMEOUT&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;61 of 156 (39%) reach full startup with zero modifications: gitea, jellyfin, grafana, home-assistant, minio, syncthing, nginx-proxy-manager, code-server — the self-hosting staples just come up.&lt;/p&gt;

&lt;p&gt;Read 39% as a conservative floor. Secrets are unfilled, so any stack that requires a DB password can't get past PARTIAL by construction. The 40 STARTED-NONERUN projects failed for mixed reasons (instant exits, pull failures) that I didn't audit one by one, so I can't even claim all 40 are "broken". Set things up the way the README says and the number only goes up.&lt;/p&gt;

&lt;p&gt;For scale: the official 29-sample survey had 14 working as-is (48%). This corpus is measured under much harsher rules — no secrets, all services must be running — and still lands at 39%. My pre-sweep prediction that real-world files would do far worse than polished samples was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counting the failures
&lt;/h2&gt;

&lt;p&gt;Classifying the 95 failures gives two groups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Group 1: nothing to do with Apple's container
&lt;/h3&gt;

&lt;p&gt;Configuration that isn't there yet, essentially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unset secrets crash the DB (the main source of PARTIAL — this is my test condition, not a bug)&lt;/li&gt;
&lt;li&gt;Host port already taken (5000/7000 is usually macOS's AirPlay Receiver; explicit mappings like &lt;code&gt;"5000:5000"&lt;/code&gt; fail identically under docker compose. The one real difference is bare ports like &lt;code&gt;- "3000"&lt;/code&gt;: docker compose escapes to a random host port, Apple's runtime requires an explicit one, so it mirrors the number and can collide)&lt;/li&gt;
&lt;li&gt;Bind mounts of placeholder paths like &lt;code&gt;/path/to/my/data&lt;/code&gt; (you're meant to fill those in)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;external&lt;/code&gt; networks that don't exist (you're meant to create the proxy network first)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For reference, the first sweep of this corpus counted the top diagnostics: 20 DB crashes from unset secrets, 12 host-port collisions, 12 placeholder-path bind failures, 10 missing external networks. All of these would need the same adjustments on Docker Desktop or on any other machine (with a caveat — see the limits section).&lt;/p&gt;

&lt;h3&gt;
  
  
  Group 2: failures that are Apple-container-specific
&lt;/h3&gt;

&lt;p&gt;Two kinds here. One is &lt;code&gt;/var/run/docker.sock&lt;/code&gt; mounts — tools that operate Docker itself, Portainer-style, 18 in the first sweep. Apple's runtime has no Docker-compatible socket, so no compose edit can save these.&lt;/p&gt;

&lt;p&gt;The other kind is the runtime model difference: places where Apple's &lt;code&gt;container&lt;/code&gt; is architecturally unlike Docker, so compose patterns that work on Docker fall over. These are fixable by changing what the compose says, and they collapse into just two patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pattern 1: a data directory on a named volume, where Postgres's initdb refuses to initialize because the directory "isn't empty" (19 of 156 affected)&lt;/li&gt;
&lt;li&gt;Pattern 2: a bind-mounted data directory that MySQL/Postgres can't &lt;code&gt;chown&lt;/code&gt;, so the entrypoint dies immediately (31 of 156 affected; 18 overlap with pattern 1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;32 projects in total, a third of all failures. Both are perfectly ordinary compose idioms on Docker — that's the point. The behavior below is as of &lt;code&gt;container&lt;/code&gt; 1.1.0.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pattern 1: named volumes are mount points
&lt;/h4&gt;

&lt;p&gt;A Docker named volume is just a directory. An Apple &lt;code&gt;container&lt;/code&gt; named volume is a whole filesystem, mounted. ext4 creates a &lt;code&gt;lost+found&lt;/code&gt; recovery directory (where fsck puts rescued files) at the root of every filesystem it makes — the one you see under &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;/home&lt;/code&gt; on any Linux box. So the volume is non-empty from the moment it's mounted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Docker:            volume = empty directory
Apple container:   volume = ext4 mount point (lost+found included)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Postgres's initdb refuses a non-empty data directory. Which means &lt;code&gt;-v pgdata:/var/lib/postgresql/data&lt;/code&gt; — a line written in thousands of compose files — doesn't work as-is. The fix is one env var: point &lt;code&gt;PGDATA&lt;/code&gt; one level below the mount point.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pattern 2: bind mounts can't be chowned
&lt;/h4&gt;

&lt;p&gt;Apple's runtime gives every container its own lightweight VM, and bind mounts reach into that VM via virtiofs — a protocol that exposes a host directory to a VM as a shared filesystem. The files are only &lt;em&gt;visible&lt;/em&gt; through the share; their owner stays the host. So &lt;code&gt;chown&lt;/code&gt; from inside the container fails. MySQL and Postgres images chown their data directory at the top of the entrypoint. Result: instant death by &lt;code&gt;Operation not permitted&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Self-hosting compose files love bind-mounting data to host paths like &lt;code&gt;/mnt/docker-volumes/&amp;lt;app&amp;gt;/&lt;/code&gt;, which is why this pattern hits so often. The fix: put the data directory (just that) on a named volume — those chown fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  If the fix is deterministic, a machine can apply it
&lt;/h2&gt;

&lt;p&gt;Both patterns have a mechanical diagnosis and a single known fix. So instead of making humans read the caveats section, I taught opossum to do it. In migration mode (&lt;code&gt;up --from-docker-compose&lt;/code&gt;) it detects the known patterns and writes only the fixes into a separate file, &lt;code&gt;compose.opossum.yaml&lt;/code&gt;, layered on top of your compose at startup. Two design rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The original &lt;code&gt;docker-compose.yml&lt;/code&gt; is never touched. docker compose doesn't know this filename and ignores it, so the same directory keeps working with both tools&lt;/li&gt;
&lt;li&gt;Every generated fix carries a comment explaining what changed, why, how to verify it, and how to undo it. The intended reader is not just you but also whatever AI agent is working on your project — the file explains itself without context&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So how many did it save?
&lt;/h2&gt;

&lt;p&gt;Same 156 projects, same binary, arm A (no fix) vs arm B (auto-fix):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;A: no fix&lt;/th&gt;
&lt;th&gt;B: auto-fix&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FULL-UP&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;+17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PARTIAL&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;−14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRASH&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;−4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STARTED-NONERUN&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PREFLIGHT&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIMEOUT&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;+1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full per-project ledger (one row per project: A class, B class, which fixes were written) and the sweep harness are &lt;a href="https://gist.github.com/suruseas/ca7ff4a3f9096bebf30a5ecf4da20e9d" rel="noopener noreferrer"&gt;published as a gist&lt;/a&gt;. Every number from here on can be recounted from that file.&lt;/p&gt;

&lt;p&gt;A summary table can hide regressions that happen to be offset by improvements, so check the ledger row by row: nothing got worse. All 61 projects that were FULL-UP in A stayed FULL-UP in B (zero collateral damage from the auto-fix). What moved: 16 PARTIAL and 1 CRASH graduated to FULL-UP, 2 CRASH became PARTIAL, 1 CRASH became TIMEOUT — 20 changes, all in the improving direction. Everything else kept its class. bitwarden (passwords), paperless-ngx (document scanning), seafile (file sync), matomo (analytics), sonarqube (code quality) — apps that all carry a Postgres or MySQL behind them, which is exactly why they were tripping over the two data-directory patterns.&lt;/p&gt;

&lt;p&gt;Where the 32 generated fix files went: 17 graduated to FULL-UP (8 from pattern 2 alone, 8 from both patterns, 1 from pattern 1 alone). The other 15 got a correct fix but stayed down behind a different wall — port collisions, docker.sock — one project can fail for several reasons at once, and now there's a number on that.&lt;/p&gt;

&lt;h3&gt;
  
  
  The timeout misclassified one project
&lt;/h3&gt;

&lt;p&gt;TIMEOUT went 2 → 3. I looked at the new one: the auto-fix got it past its CRASH, which meant initialization finally ran to completion — in 154 seconds, past the 120-second cutoff, with every service up. Classified TIMEOUT, actually a success.&lt;/p&gt;

&lt;p&gt;Count it and you get 79 (51%). But arm A has two TIMEOUTs of its own that I did not audit the same way, and reclassifying only the borderline cases that flatter the result isn't fair. So this article's number stays what the table says: 78.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;78 of 156 — 39% to 50%. Half of a real-world compose corpus reaches full startup without you editing a single line.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One measurement lesson for the road: a hard timeout is necessary if you don't want a sweep to hang forever, and it will still misclassify the borderline. Read every guarded measurement as a floor. Both 39% and 50% here are floors in that sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I didn't do / can't claim
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One machine, one pass per arm. The 23 PREFLIGHTs include this Mac's own port situation; some would pass elsewhere&lt;/li&gt;
&lt;li&gt;Unfilled secrets is not how anyone actually runs these stacks. Filling them would convert much of PARTIAL to FULL-UP; I didn't measure that&lt;/li&gt;
&lt;li&gt;The sweep runs below any &lt;code&gt;restart&lt;/code&gt; policy (143 of the 159 files declare one). Docker compose would absorb some first-boot crashes — a DB winning a startup race on retry — via &lt;code&gt;restart: always&lt;/code&gt;, so part of PARTIAL/CRASH may be my measuring layer's behavior rather than runtime incompatibility. Not yet isolated&lt;/li&gt;
&lt;li&gt;No docker compose control run. "These would need the same adjustments on Docker" is inferred from the failure logs, not measured&lt;/li&gt;
&lt;li&gt;The pattern-2 fix moves where your data lives (bind mount → named volume). Harmless for fresh stacks; migrating existing data is out of scope&lt;/li&gt;
&lt;li&gt;docker.sock, kernel modules (WireGuard), cgroup-reading JVMs (old Elasticsearch): not fixable by editing compose, still broken&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What counting taught me
&lt;/h2&gt;

&lt;p&gt;With 29 official samples I thought about compatibility as a table: this field supported, that field not. What 156 real-world files taught me is that the real picture is a distribution — what fraction just works, and where the failures cluster. Measured that way: 40% works untouched, most failures are configuration rather than the runtime, the runtime-specific failures collapse into a couple of mechanically fixable patterns, and the unfixable remainder is the part that depends on Docker-the-infrastructure — which stays broken until Apple ships a compatible socket or volume seeding.&lt;/p&gt;

&lt;p&gt;Apple's container compatibility story has moved from "not yet" to "fails predictably". Predictable failures are automatable fixes. And the 156-project ledger points at exactly which gap should close next.&lt;/p&gt;

&lt;p&gt;If you run your own compose files through this, I'd love to hear how they land — especially the ones that break in ways not on this list.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>Apple's container makes no sense as a Docker replacement — until you see it as a box for AI agents</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:36:52 +0000</pubDate>
      <link>https://dev.to/suruseas/apples-container-makes-no-sense-as-a-docker-replacement-until-you-see-it-as-a-box-for-ai-agents-4i13</link>
      <guid>https://dev.to/suruseas/apples-container-makes-no-sense-as-a-docker-replacement-until-you-see-it-as-a-box-for-ai-agents-4i13</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;日本語版 (Japanese version): &lt;a href="https://zenn.dev/suruseas/articles/1ff754eeefd329" rel="noopener noreferrer"&gt;Apple containerの不可解な設計、実は"AIエージェント用の箱"を作ってるだけ説&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A design that keeps losing benchmarks
&lt;/h2&gt;

&lt;p&gt;When I &lt;a href="https://dev.to/suruseas/apples-container-hit-10-should-it-be-your-dev-environment-yet-i-measured-1e00"&gt;measured Apple's &lt;code&gt;container&lt;/code&gt; runtime&lt;/a&gt;, the honest numbers were rough: every container gets its own VM at roughly 270–400 MB, throwaway containers start 4–10× slower than Docker's, and a named volume can only attach to one running container at a time. Judged as a docker compose replacement, it looks like Apple deliberately picked a losing design.&lt;/p&gt;

&lt;p&gt;And that's the puzzle. Apple obviously knows how Docker Desktop's shared-VM model wins on density — that design has had a decade of tuning. They looked at it and walked away anyway. Why?&lt;/p&gt;

&lt;p&gt;Here's the hypothesis this post explores: &lt;strong&gt;Apple isn't building a box for your dev stack. It's building a box for code you don't trust.&lt;/strong&gt; And in 2026, the most important untrusted code around is the AI coding agent. Read the design through that lens and every "flaw" starts looking like a decision.&lt;/p&gt;

&lt;p&gt;This is an interpretation piece, but every claim below is anchored to something measured on my machine or shipped in public. At the end I'll list predictions that would falsify the whole reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exhibit 1: the parts list is suspicious
&lt;/h2&gt;

&lt;p&gt;Line up what Apple has shipped over the past year — as parts, not features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-VM isolation.&lt;/strong&gt; Each container gets its own lightweight VM with its own kernel. Worst possible choice for "many containers, cheaply"; best possible choice for "one untrusted thing, strongly isolated."&lt;/p&gt;

&lt;p&gt;For contrast: Microsoft went the opposite way the very same month. &lt;a href="https://devblogs.microsoft.com/commandline/wsl-container-is-now-available-for-public-preview/" rel="noopener noreferrer"&gt;WSL containers&lt;/a&gt; (public preview, June 2026) run in one shared utility VM with namespace isolation — the same "many, cheaply" side Docker sits on. Two OS vendors shipped first-party containers at the same time and made opposite isolation choices. That tells you what each was optimizing for.&lt;/p&gt;

&lt;p&gt;But here's the interesting part: &lt;strong&gt;Docker itself picked per-VM when the workload is an agent.&lt;/strong&gt; &lt;a href="https://www.docker.com/blog/docker-sandboxes-run-claude-code-and-other-coding-agents-unsupervised-but-safely/" rel="noopener noreferrer"&gt;Docker Sandboxes&lt;/a&gt; (January 2026) isolates Claude Code, Codex, and friends in a dedicated microVM per agent. The company whose whole model is "share one kernel, run many" chose a VM boundary the moment the tenant became untrusted code. Two players, different starting points, same answer. I take that as evidence about what these boxes are &lt;em&gt;for&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;mcpbridge.&lt;/strong&gt; Apple &lt;a href="https://www.apple.com/newsroom/2026/02/xcode-26-point-3-unlocks-the-power-of-agentic-coding/" rel="noopener noreferrer"&gt;officially announced agentic coding in Xcode 26.3&lt;/a&gt;, exposing Xcode's capabilities over MCP. The implementation is a bundled binary, &lt;code&gt;xcrun mcpbridge&lt;/code&gt;, that translates MCP into XPC — Apple's entitlement-gated IPC. A portability protocol, deliberately re-rooted into Apple's permission model: MCP tools treated as capabilities, the same shape as iOS app permissions. In the Xcode 27 beta there's a second layer: &lt;a href="https://agentclientprotocol.com/" rel="noopener noreferrer"&gt;ACP (Agent Client Protocol)&lt;/a&gt; — an open protocol from Zed, not Apple — governs &lt;em&gt;which agents may connect at all&lt;/em&gt;. MCP says what an agent can do; ACP says who gets in. Two gates, both built from other companies' open standards folded into Apple's own permission model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Models on the host, tools in the box.&lt;/strong&gt; Apple &lt;code&gt;container&lt;/code&gt; has no GPU/ANE passthrough. Inference was never meant to live in the container: Foundation Models run host-side on the Neural Engine, and the containers hold the tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container machines.&lt;/strong&gt; WWDC26 also brought persistent Linux VMs — the long-lived counterpart to &lt;code&gt;container run&lt;/code&gt;'s disposable ones.&lt;/p&gt;

&lt;p&gt;Boundaries and gates everywhere. That's a lot of parts for a "Docker alternative."&lt;/p&gt;

&lt;h2&gt;
  
  
  Exhibit 2: this is how Apple has always done safety
&lt;/h2&gt;

&lt;p&gt;There's a lineage here. &lt;a href="https://security.apple.com/blog/private-cloud-compute/" rel="noopener noreferrer"&gt;Private Cloud Compute&lt;/a&gt;'s design principle was "don't trust the operator — prove it structurally." The iOS sandbox is the same shape: apps run &lt;em&gt;free&lt;/em&gt; inside the sandbox, and hit a checkpoint only when crossing a boundary — contacts, camera, location. The checkpoint is an entitlement check or that permission dialog you've tapped a thousand times. Call it the gate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;free inside the boundary → one gate at the crossing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now compare that to how today's coding agents are kept safe: an approval prompt for every action, a human in the loop for each step. Apple's pattern is the inverse — total freedom inside, one gate at the edge. Per-VM containers are the boundary; mcpbridge + ACP are the gate. The parts line up with the philosophy.&lt;/p&gt;

&lt;p&gt;For agents this difference is fundamental. Per-action approval burns human attention continuously. A boundary model justifies &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; structurally: whatever happens, it happens inside the box. Since an agent's whole value is autonomous, continuous operation, I'd bet agent platforms converge on the boundary model. That bet is the core of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exhibit 3: you can already build the box — with a compose file
&lt;/h2&gt;

&lt;p&gt;Here's the observation I most want to share. To run the boundary model on a Mac today you need some way to &lt;em&gt;declare&lt;/em&gt; what the agent gets. iOS has entitlements for this. The container world, it turns out, already has a vocabulary for it — the compose file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;volumes:&lt;/code&gt; (bind mount) = the files the agent may see&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;environment:&lt;/code&gt; + &lt;code&gt;.env&lt;/code&gt; = the secrets it holds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ports:&lt;/code&gt; / host gateway = its reach back to the host&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;networks:&lt;/code&gt; / &lt;code&gt;network_mode:&lt;/code&gt; = how far it reaches out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't want to leave that as theory, so I built it. (Apple &lt;code&gt;container&lt;/code&gt; doesn't read compose files itself; I used &lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;opossum&lt;/a&gt;, a compose-compatible orchestrator I work on, as the translator — but the star here is the compose vocabulary, not the tool.)&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;            &lt;span class="c1"&gt;# node:22 + git + Claude Code&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;          &lt;span class="c1"&gt;# non-root: Claude Code refuses full-auto mode as root&lt;/span&gt;
    &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/work&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./work:/work&lt;/span&gt;    &lt;span class="c1"&gt;# the ONLY host files the agent sees; results land here&lt;/span&gt;
    &lt;span class="na"&gt;mem_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4g&lt;/span&gt;       &lt;span class="c1"&gt;# the default ~1 GiB guest is tight for node + build + test&lt;/span&gt;
    &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CLAUDE_CODE_OAUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CLAUDE_CODE_OAUTH_TOKEN:-}&lt;/span&gt;   &lt;span class="c1"&gt;# value lives in .env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read differently, this file is a capability manifest: &lt;em&gt;this agent sees &lt;code&gt;./work&lt;/code&gt; and nothing else, holds this one token, gets 4 GB&lt;/em&gt;. No new machinery — ten-year-old compose vocabulary, reread.&lt;/p&gt;

&lt;p&gt;Does it actually work? On my machine (macOS 26, container 1.1.0): I gave the in-VM Claude Code one line — "fix the failing test in /work/demo and commit" — in full-auto mode. It found the bug (an addition function doing &lt;code&gt;a - b&lt;/code&gt;), fixed it, ran the tests, and committed, with zero human input. The results appeared on the host via the bind mount. Whatever the agent had done in there, the only thing that could reach my Mac was the contents of &lt;code&gt;./work&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And the benchmark numbers that looked so bad in my first post? For this workload they stop mattering. Agent tasks run for minutes, so a ~1 s VM boot is noise; 270–400 MB per VM is nothing when you're granting the agent 4 GB anyway. The puzzle from the intro resolves.&lt;/p&gt;

&lt;h3&gt;
  
  
  The weak side: egress
&lt;/h3&gt;

&lt;p&gt;Files, secrets, and host reach are all declarable. Outbound network is the rough edge. What I measured (macOS 26 / container 1.1.0):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Possible today?&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Block everything&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;network_mode: none&lt;/code&gt; (loopback only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block internet, keep host&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;an internal network (&lt;code&gt;--internal&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination allowlist&lt;/td&gt;
&lt;td&gt;✅ (enforced)&lt;/td&gt;
&lt;td&gt;internal network + an allowlist proxy on the host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native per-container domain allowlist&lt;/td&gt;
&lt;td&gt;❌ not yet (Apple; Docker Sandboxes has it — see below)&lt;/td&gt;
&lt;td&gt;(prediction #1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Row three is the one worth knowing. On an internal network the internet route physically disappears, so the only way out is a proxy you run on the host. An agent that ignores &lt;code&gt;http_proxy&lt;/code&gt; and connects directly simply gets nothing. The allowlist is &lt;em&gt;enforced&lt;/em&gt;, not advisory. (In-guest iptables allowlists, for the record, didn't work in my tests — allowed destinations dropped too. The proxy approach is the right one.)&lt;/p&gt;

&lt;h2&gt;
  
  
  "Why not just use Docker Sandboxes?"
&lt;/h2&gt;

&lt;p&gt;Fair question — I asked it myself, so I put it through its paces (Docker Desktop 4.78, &lt;code&gt;docker sandbox&lt;/code&gt; v0.12):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker sandbox create shell &lt;span class="nb"&gt;.&lt;/span&gt;        &lt;span class="c"&gt;# a box with the current dir as workspace&lt;/span&gt;
docker sandbox &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;box&amp;gt; &lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;   &lt;span class="c"&gt;# → Linux ... 6.12.67-linuxkit aarch64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a real microVM: a linuxkit-kernel Linux, one per agent — same idea as Apple's per-VM&lt;/li&gt;
&lt;li&gt;The workspace mounts at the same path as the host&lt;/li&gt;
&lt;li&gt;It has the native domain allowlist Apple lacks:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker sandbox network proxy &amp;lt;box&amp;gt; &lt;span class="nt"&gt;--policy&lt;/span&gt; deny &lt;span class="nt"&gt;--allow-host&lt;/span&gt; example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that set, &lt;code&gt;example.com&lt;/code&gt; returns 200 from inside the box and anything else gets 403. Better: bypassing the proxy env vars entirely (&lt;code&gt;curl --noproxy '*'&lt;/code&gt;) fails even for the &lt;em&gt;allowed&lt;/em&gt; host — there is no direct route out, the MITM proxy is the only exit. Enforced, same property as the internal-network setup above. The box even ships a docker CLI inside, so containers-in-VM works out of the box.&lt;/p&gt;

&lt;p&gt;Honestly, as an agent box today, Docker Sandboxes is the most complete option: native allowlists, multiple agents, &lt;code&gt;save&lt;/code&gt; for snapshots, &lt;code&gt;reset&lt;/code&gt; in seconds.&lt;/p&gt;

&lt;p&gt;What interests me is not which is better but that &lt;strong&gt;the two approaches express the box's permissions differently&lt;/strong&gt;. Docker Sandboxes is imperative: you run commands to configure the box. The compose approach is declarative: mounts, env, and networks sit in one file. A procedure has to be executed to be understood; a declaration can just be &lt;em&gt;read&lt;/em&gt; — by a human or by another agent. In a world where agents build and audit each other's boxes, I'd expect the readable form to win. But that's a forecast, not a verdict: what we can observe today is two vocabularies — imperative and declarative — emerging in parallel for the same per-VM box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exhibit 4: the same principle, invented three times
&lt;/h2&gt;

&lt;p&gt;"Free inside, gate at the boundary" isn't only Apple's pattern. The players differ in &lt;em&gt;what they build the boundary out of&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The spatial layer&lt;/strong&gt; — physical walls. VMs: places the agent simply cannot reach&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The semantic layer&lt;/strong&gt; — judging the meaning of an action. "Is this force push dangerous?" A checkpoint officer instead of a wall&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The resource layer&lt;/strong&gt; — shrinking what's handed over in the first place. Mounts and env: can't leak what you don't hold&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Main implementation&lt;/th&gt;
&lt;th&gt;Declared in&lt;/th&gt;
&lt;th&gt;Enforced by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spatial (Apple)&lt;/td&gt;
&lt;td&gt;VM boundary, entitlements&lt;/td&gt;
&lt;td&gt;entitlements / VM config&lt;/td&gt;
&lt;td&gt;kernel, hypervisor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic (Anthropic)&lt;/td&gt;
&lt;td&gt;the auto-mode classifier&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;autoMode.environment&lt;/code&gt; (prose)&lt;/td&gt;
&lt;td&gt;a classifier LLM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource (compose)&lt;/td&gt;
&lt;td&gt;mounts / env / networks&lt;/td&gt;
&lt;td&gt;compose.yaml&lt;/td&gt;
&lt;td&gt;VM configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Main" because nobody stays in one layer: Anthropic also ships a &lt;a href="https://www.anthropic.com/engineering/claude-code-sandboxing" rel="noopener noreferrer"&gt;process-level spatial layer&lt;/a&gt; (Seatbelt on macOS, bubblewrap on Linux, with a proxy-enforced domain allowlist — strikingly similar to the egress setup above). It's an OS-process wall, though; a per-VM kernel wall is a harder material, and that's Apple's turf.&lt;/p&gt;

&lt;p&gt;The semantic layer deserves a note. Claude Code's &lt;a href="https://code.claude.com/docs/en/auto-mode-config" rel="noopener noreferrer"&gt;auto-mode&lt;/a&gt; routes every tool call through a classifier that blocks only the irreversible, destructive, or out-of-environment. Its boundary is declared in &lt;em&gt;prose&lt;/em&gt; ("Source control: github.example.com/acme-corp"), and its precedence ends with explicit user intent: saying "force push this branch" clears a soft block. Conversational context becomes part of authorization — something a VM or an entitlement can't do in principle. Conversely, a probabilistic classifier can't hard-limit the blast radius of arbitrary code execution. Only the spatial layer can.&lt;/p&gt;

&lt;p&gt;So the "capability manifest" has been invented three times — as entitlements, as prose, as compose.yaml — and the three don't compete. They stack, like defense in depth. They already interlock in one concrete place: auto-mode's built-in rules (dump them with &lt;code&gt;claude auto-mode defaults&lt;/code&gt;) block launching an unsupervised agent loop only when it runs "without sandbox isolation AND without a per-action approval/monitor gate." Declare the spatial layer, and the semantic layer steps aside. The sandbox from Exhibit 3 satisfies exactly that condition — via a compose file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Are the benchmark downsides real?&lt;/td&gt;
&lt;td&gt;✅ Real (my earlier measurements stand)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Are the "agent box" parts all shipped?&lt;/td&gt;
&lt;td&gt;✅ per-VM, mcpbridge + ACP, host-side models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can you build the box today?&lt;/td&gt;
&lt;td&gt;✅ compose vocabulary + a real autonomous end-to-end run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Has anyone else made the same choice?&lt;/td&gt;
&lt;td&gt;✅ Docker Sandboxes, from the opposite starting point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Has Apple said "this is an agent box"?&lt;/td&gt;
&lt;td&gt;❌ Never, anywhere. That part is my reading&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The parts exist, the philosophy matches, and the box demonstrably works. What's missing is Apple saying so. Circumstantial evidence everywhere; the suspect remains silent.&lt;/p&gt;

&lt;p&gt;So, predictions — ordered by my confidence, so this reading can be scored later. If these miss, the title was just my imagination:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;apple/container ships per-container egress control.&lt;/strong&gt; This is the big one. Docker Sandboxes already has native allowlists, and there's &lt;a href="https://github.com/apple/container/discussions/1170" rel="noopener noreferrer"&gt;an open Discussion asking for network isolation&lt;/a&gt;. If two years pass with nothing, discard this whole post&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mcpbridge escapes Xcode.&lt;/strong&gt; An MCP→XPC gate is far too general-purpose to stay a single IDE's feature&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container machines grow agent-workspace features.&lt;/strong&gt; A box you let agents experiment in badly needs "undo": Docker Sandboxes' &lt;code&gt;save&lt;/code&gt;/&lt;code&gt;reset&lt;/code&gt; is exactly that. If snapshots/rollback land on container machines, that's the tell&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A first-party API connecting Foundation Models to tools running in containers&lt;/strong&gt; — Apple wiring up its own "models on the host, tools in the box" split&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attestation for agent runs&lt;/strong&gt;: a verifiable proof that &lt;em&gt;this artifact was produced inside that box&lt;/em&gt; (a cousin of GitHub's Artifact Attestations). Far off, but it's the natural endpoint of the PCC lineage — proof by structure, not trust&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Apple has shipped host-side agents with gated tools (mcpbridge/ACP). Running the agent &lt;em&gt;inside&lt;/em&gt; the VM is my extrapolation; I verified that it works, not that Apple intends it&lt;/li&gt;
&lt;li&gt;The benchmark downsides have not gone away. I still don't recommend moving your dev stack (see the &lt;a href="https://dev.to/suruseas/apples-container-hit-10-should-it-be-your-dev-environment-yet-i-measured-1e00"&gt;measurements&lt;/a&gt;); this post is about a different objective, not revised numbers&lt;/li&gt;
&lt;li&gt;The VM protects the &lt;em&gt;host&lt;/em&gt;. Whatever you bring inside — the mounted &lt;code&gt;./work&lt;/code&gt;, the token — is inside the blast radius. A manifest declares what you hand over; it doesn't protect what you've handed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Apple &lt;code&gt;container&lt;/code&gt;'s "inexplicable" design becomes coherent the moment the box is for untrusted code rather than for your dev stack&lt;/li&gt;
&lt;li&gt;The parts — per-VM boundaries, mcpbridge (MCP→XPC), ACP, host-side models — sit squarely in the PCC lineage of &lt;em&gt;structure over trust&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The vocabulary to build such a box already exists: a compose file works as an agent's capability manifest, and a real agent completed a task fully autonomously inside one&lt;/li&gt;
&lt;li&gt;The same principle now exists at three layers — spatial, semantic, resource — and they stack rather than compete&lt;/li&gt;
&lt;li&gt;The scoreboard: whether apple/container ships per-container egress control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Models will keep getting smarter on their own. The contest is moving to the outside: what you hand an agent, how far you let it reach, how you check what it did. Apple writes that in entitlements, Anthropic writes it in prose, and the container world writes it in ten-year-old compose files. The next fight is over how the box gets declared.&lt;/p&gt;

&lt;p&gt;If you can refute any prediction on the list, I genuinely want to hear it — comments here or wherever you found this.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>macos</category>
      <category>ai</category>
      <category>security</category>
    </item>
    <item>
      <title>Your Claude Code MCP servers are quietly costing you gigabytes of RAM</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Fri, 17 Jul 2026 10:31:09 +0000</pubDate>
      <link>https://dev.to/suruseas/your-claude-code-mcp-servers-are-quietly-costing-you-gigabytes-of-ram-1me7</link>
      <guid>https://dev.to/suruseas/your-claude-code-mcp-servers-are-quietly-costing-you-gigabytes-of-ram-1me7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;日本語版は Zenn にあります: &lt;a href="https://zenn.dev/suruseas/articles/91114af8301137" rel="noopener noreferrer"&gt;Claude Code の MCP サーバー、実はメモリ数GB食ってますよ&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;.mcp.json&lt;/code&gt; you probably have
&lt;/h2&gt;

&lt;p&gt;If you've wired MCP servers into Claude Code (or Cursor), yours probably looks&lt;br&gt;
like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--rm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hashicorp/terraform-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;docker&lt;/code&gt; means Docker Desktop is running — resident, all day — to host&lt;br&gt;
this. Open Activity Monitor and look: "Virtual Machine Service for Docker"&lt;br&gt;
alone is over 1 GB, plus the backend and UI processes around it.&lt;/p&gt;

&lt;p&gt;Now consider what an MCP server actually does. It only exists during a session,&lt;br&gt;
and even then it's idle ~99% of the time, waiting for a tool call. You're&lt;br&gt;
keeping a gigabyte-class VM warm around the clock for a tool shed that's&lt;br&gt;
almost always asleep.&lt;/p&gt;

&lt;p&gt;On macOS 26 you can take that memory back with a one-line swap to Apple's&lt;br&gt;
first-party &lt;a href="https://github.com/apple/container" rel="noopener noreferrer"&gt;&lt;code&gt;container&lt;/code&gt;&lt;/a&gt; runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add terraform &lt;span class="nt"&gt;--&lt;/span&gt; container run &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; hashicorp/terraform-mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole migration. No Docker Desktop, no extra tooling. I verified&lt;br&gt;
this with Docker Desktop fully quit — zero &lt;code&gt;docker&lt;/code&gt; processes alive — and&lt;br&gt;
Claude Code tool calls still going through.&lt;/p&gt;

&lt;p&gt;(If this is your first time with Apple's &lt;code&gt;container&lt;/code&gt;, install and start the&lt;br&gt;
runtime first — &lt;code&gt;brew install container&lt;/code&gt;, then &lt;code&gt;container system start&lt;/code&gt;.&lt;br&gt;
Details in the migration section below.)&lt;/p&gt;

&lt;p&gt;The rest of this post covers why this workload is the one place Apple&lt;br&gt;
&lt;code&gt;container&lt;/code&gt; genuinely wins (I have measurements), where you'll outgrow the raw&lt;br&gt;
command, and the one trap I hit running it for real.&lt;/p&gt;
&lt;h2&gt;
  
  
  MCP servers are the shape Apple container is good at
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/suruseas/apples-container-hit-10-should-it-be-your-dev-environment-yet-i-measured-1e00"&gt;my previous post&lt;/a&gt;&lt;br&gt;
I measured Apple &lt;code&gt;container&lt;/code&gt; and concluded, honestly, that it's &lt;strong&gt;not&lt;/strong&gt; ready&lt;br&gt;
to be your daily dev environment: every container gets its own VM at ~270–400&lt;br&gt;
MB, and throwaway containers start 4–10× slower than Docker's. For a&lt;br&gt;
multi-service dev stack, those numbers hurt.&lt;/p&gt;

&lt;p&gt;But an MCP server inverts every one of those trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a few small images, not a stack&lt;/li&gt;
&lt;li&gt;alive only during a session, idle nearly all of it&lt;/li&gt;
&lt;li&gt;third-party code holding your API tokens — the more isolation, the better&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apple &lt;code&gt;container&lt;/code&gt; has no resident VM. Idle host-side cost is roughly 50 MB,&lt;br&gt;
and the MCP server's VM exists only while your session does. Docker Desktop's&lt;br&gt;
shared VM runs 1.4–2.1 GB &lt;strong&gt;all the time&lt;/strong&gt; for the same job. For a tool shed&lt;br&gt;
that's 99% idle, that's a bad rent.&lt;/p&gt;

&lt;p&gt;And per-VM isolation is not just a talking point here. An MCP server is&lt;br&gt;
somebody else's code holding your GitHub token. On Apple &lt;code&gt;container&lt;/code&gt;, each&lt;br&gt;
server gets its own VM — a materially stronger wall than Docker containers&lt;br&gt;
sharing one kernel.&lt;/p&gt;
&lt;h2&gt;
  
  
  Migrating (about a minute)
&lt;/h2&gt;

&lt;p&gt;Prerequisites: macOS 26 on Apple silicon, plus one-time runtime setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;container   &lt;span class="c"&gt;# or grab it from github.com/apple/container/releases&lt;/span&gt;
container system start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then swap the registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp remove terraform
claude mcp add terraform &lt;span class="nt"&gt;--&lt;/span&gt; container run &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; hashicorp/terraform-mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a new session, check &lt;code&gt;/mcp&lt;/code&gt; shows connected, call a tool once. Images are&lt;br&gt;
pulled automatically on first connect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjaz1vqxn247ohuktkb48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjaz1vqxn247ohuktkb48.png" alt="Activity Monitor with Docker Desktop running" width="800" height="561"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Docker Desktop running, one container active: the "Virtual Machine Service for Docker" alone is 1.13 GB, and the backend/UI family pushes the total past 1.4 GB. (Recent Docker Desktop versions can park the VM while no containers run — but even then the family keeps ~400 MB resident, measured.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmz4u8pvvfbg1mbam0s6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmz4u8pvvfbg1mbam0s6i.png" alt="Activity Monitor after quitting Docker Desktop" width="800" height="561"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;After quitting Docker Desktop: all that's left is &lt;code&gt;com.docker.vmnetd&lt;/code&gt;, a 4.4 MB resident LaunchDaemon.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr31vqftn82sxyn2th7zr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr31vqftn82sxyn2th7zr.png" alt="Activity Monitor with Apple container running an MCP server" width="800" height="561"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Apple container running the terraform MCP server: "Virtual Machine Service for container-runtime-linux" at 275 MB plus a few single-digit-MB helpers. The &lt;code&gt;containermanagerd&lt;/code&gt; and &lt;code&gt;ContainerMetadataExtractor&lt;/code&gt; rows are stock macOS daemons that just happen to match the search — nothing to do with Apple container.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After quitting Docker Desktop, Activity Monitor shows the Docker family&lt;br&gt;
(backend + VM + UI, over 1 GB combined) gone. In its place: one "Virtual&lt;br&gt;
Machine Service for container-runtime-linux" at ~280 MB and a ~20 MB helper —&lt;br&gt;
and only while a session is open.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where the raw command stops being enough
&lt;/h2&gt;

&lt;p&gt;A bare &lt;code&gt;container run&lt;/code&gt; covers exactly one case: a single stdio server with no&lt;br&gt;
secrets. Hit any of the following and it's time to graduate to a compose file.&lt;br&gt;
I'll use &lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;opossum&lt;/a&gt;, the compose-compatible&lt;br&gt;
orchestrator for Apple &lt;code&gt;container&lt;/code&gt; I've been building, but the reasoning is&lt;br&gt;
tool-agnostic.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. A server that needs a token
&lt;/h3&gt;

&lt;p&gt;With raw commands, the token ends up inline in &lt;code&gt;.mcp.json&lt;/code&gt; — a file that's&lt;br&gt;
often project-scoped and committed. That's an incident waiting to happen. With&lt;br&gt;
compose, the token lives in a git-ignored &lt;code&gt;.env&lt;/code&gt;, and &lt;code&gt;.mcp.json&lt;/code&gt; carries only&lt;br&gt;
a command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .mcp.json — no token in sight&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"opossum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/mcp-stack/compose.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--rm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# compose.yaml — the token is injected from .env next to this file&lt;/span&gt;
  &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/github/github-mcp-server&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GITHUB_PERSONAL_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GITHUB_TOKEN:-}&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdio"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# not started by `up`; runs only when invoked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; resolves relative to the compose file, so it works no matter which&lt;br&gt;
directory your MCP client launches from.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Several servers
&lt;/h3&gt;

&lt;p&gt;At three or four servers you start wanting bulk image updates&lt;br&gt;
(&lt;code&gt;opossum pull&lt;/code&gt;), a single config listing (&lt;code&gt;opossum config&lt;/code&gt;), and one file&lt;br&gt;
that &lt;em&gt;is&lt;/em&gt; your toolbox.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. An HTTP-transport server
&lt;/h3&gt;

&lt;p&gt;Streamable-HTTP MCP servers are long-running services — that's &lt;code&gt;up&lt;/code&gt; / &lt;code&gt;down&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;logs&lt;/code&gt; territory, not &lt;code&gt;run --rm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;opossum &lt;span class="nt"&gt;-f&lt;/span&gt; mcp-stack/compose.yaml up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .mcp.json just points at the URL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"terraform-http"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080/mcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One gotcha: if the server binds its default 127.0.0.1, the published port&lt;br&gt;
can't reach it. Bind all interfaces — e.g. &lt;code&gt;--transport-host 0.0.0.0&lt;/code&gt; for&lt;br&gt;
terraform-mcp-server; other servers have an equivalent flag.&lt;/p&gt;

&lt;p&gt;A working example with all three patterns in one file is at&lt;br&gt;
&lt;a href="https://github.com/suruseas/opossum/tree/main/examples/mcp-stack" rel="noopener noreferrer"&gt;examples/mcp-stack&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The trap I actually hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Connected, but every tool call fails."&lt;/strong&gt; A stdio connection runs over&lt;br&gt;
stdin/stdout and never touches the network, so the connection looks perfectly&lt;br&gt;
healthy even when the container's outbound networking is dead. Tools fail only&lt;br&gt;
at the moment they hit an external API — and the error comes back wearing an&lt;br&gt;
innocent face like "provider not found", nothing that says &lt;em&gt;network&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In my case, outbound traffic on the default network died after the runtime had&lt;br&gt;
been up for several days. The triage is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;container run &lt;span class="nt"&gt;--rm&lt;/span&gt; alpine ping &lt;span class="nt"&gt;-c1&lt;/span&gt; 1.1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that fails, &lt;code&gt;container system stop &amp;amp;&amp;amp; container system start&lt;/code&gt; recovers it.&lt;br&gt;
If you use opossum, &lt;code&gt;opossum doctor&lt;/code&gt; runs this probe as part of a one-shot&lt;br&gt;
environment check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session start pays ~1 second of VM boot.&lt;/strong&gt; Tool calls after connection are
unaffected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Xcode users get first-party MCP integration&lt;/strong&gt; (Xcode 27 supports MCP
natively). This post is for people who live outside Xcode — Claude Code,
Cursor, and friends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I still don't recommend migrating your dev stack itself.&lt;/strong&gt; The
measurements in &lt;a href="https://dev.to/suruseas/apples-container-hit-10-should-it-be-your-dev-environment-yet-i-measured-1e00"&gt;the previous post&lt;/a&gt;
stand. MCP servers are the first real-world instance of the one shape that
post said Apple &lt;code&gt;container&lt;/code&gt; is good at: small, occasional, idle.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;On macOS 26, changing &lt;code&gt;docker&lt;/code&gt; to &lt;code&gt;container&lt;/code&gt; in &lt;code&gt;.mcp.json&lt;/code&gt; runs your MCP
servers without Docker Desktop&lt;/li&gt;
&lt;li&gt;The MCP workload (small, idle, credential-holding) matches Apple
&lt;code&gt;container&lt;/code&gt;'s strengths exactly&lt;/li&gt;
&lt;li&gt;Graduate to compose when you hit tokens, server sprawl, or HTTP transport —
until then the raw command is enough&lt;/li&gt;
&lt;li&gt;If tools fail while showing connected, &lt;code&gt;ping 1.1.1.1&lt;/code&gt; from a container first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent toolboxes only grow from here, and the shed they live in should be light&lt;br&gt;
and isolated. Models keep getting smarter on their own — the leverage is in&lt;br&gt;
the tooling around them, and there's no reason that tooling should cost you&lt;br&gt;
gigabytes of always-on RAM.&lt;/p&gt;

&lt;p&gt;If you run your MCP setup on Apple &lt;code&gt;container&lt;/code&gt;, I'd love to hear how it went —&lt;br&gt;
comments or &lt;a href="https://github.com/suruseas/opossum/issues" rel="noopener noreferrer"&gt;issues&lt;/a&gt; both work.&lt;br&gt;
"Here's what happened with my config" is plenty.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>macos</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Apple's container hit 1.0. Should it be your dev environment yet? I measured.</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Tue, 14 Jul 2026 14:56:57 +0000</pubDate>
      <link>https://dev.to/suruseas/apples-container-hit-10-should-it-be-your-dev-environment-yet-i-measured-1e00</link>
      <guid>https://dev.to/suruseas/apples-container-hit-10-should-it-be-your-dev-environment-yet-i-measured-1e00</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Apple's &lt;a href="https://github.com/apple/container" rel="noopener noreferrer"&gt;&lt;code&gt;container&lt;/code&gt;&lt;/a&gt; runtime reached &lt;strong&gt;1.0&lt;/strong&gt;&lt;br&gt;
in June (1.1.0, a bug-fix patch, is current as I write), and on macOS 26&lt;br&gt;
containers can finally talk to each other — which is the last piece needed to&lt;br&gt;
run a multi-service dev stack. The pieces are, genuinely, all here now.&lt;/p&gt;

&lt;p&gt;So I wanted an honest answer to the practical question: &lt;strong&gt;should you switch your&lt;br&gt;
daily dev environment to it today?&lt;/strong&gt; I measured two things that decide that —&lt;br&gt;
&lt;em&gt;compatibility&lt;/em&gt; (does your &lt;code&gt;docker-compose.yml&lt;/code&gt; run?) and &lt;em&gt;performance&lt;/em&gt; (is it&lt;br&gt;
actually pleasant to live in?). The short version, up front, so I'm not burying&lt;br&gt;
the lede:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Compatibility is real — about half of the compose files I tried ran&lt;br&gt;
unmodified, and ~60% within a one-line fix. But performance is behind&lt;br&gt;
today&lt;/strong&gt;, and for a normal multi-service dev stack that's the part you feel.&lt;br&gt;
If you want a Docker Desktop replacement &lt;em&gt;right now&lt;/em&gt;, Docker itself, Colima,&lt;br&gt;
or OrbStack (the established alternatives — all built on the same shared-VM&lt;br&gt;
model as Docker) are the pragmatic picks. Apple &lt;code&gt;container&lt;/code&gt; is the better&lt;br&gt;
choice today in exactly one shape: &lt;strong&gt;a small image or two, run occasionally.&lt;/strong&gt;&lt;br&gt;
I'd watch it closely — but I wouldn't move my main dev stack onto it yet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the data behind that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 1 — Compatibility: better than I expected
&lt;/h2&gt;

&lt;p&gt;I took Docker's own&lt;br&gt;
&lt;a href="https://github.com/docker/awesome-compose" rel="noopener noreferrer"&gt;awesome-compose&lt;/a&gt; repository — the&lt;br&gt;
official collection of sample compose projects (WordPress, React+Express+Mongo,&lt;br&gt;
Spring+Postgres, Prometheus+Grafana, …) — and ran &lt;strong&gt;29 of its samples,&lt;br&gt;
unmodified&lt;/strong&gt;, on Apple &lt;code&gt;container&lt;/code&gt;. An alphabetical slice, no cherry-picking:&lt;br&gt;
every sample from &lt;code&gt;nextcloud-postgres&lt;/code&gt; through &lt;code&gt;wordpress-mysql&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;Apple's &lt;code&gt;container&lt;/code&gt; is a runtime, not an orchestrator: it has no &lt;code&gt;compose&lt;/code&gt;&lt;br&gt;
subcommand, no dependency ordering, no service discovery. For that I used&lt;br&gt;
&lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;opossum&lt;/a&gt;, a small compose-like&lt;br&gt;
orchestrator for Apple &lt;code&gt;container&lt;/code&gt; that I've been building. It reads your&lt;br&gt;
existing &lt;code&gt;compose.yaml&lt;/code&gt; / &lt;code&gt;docker-compose.yml&lt;/code&gt; and gives you the familiar&lt;br&gt;
verbs — &lt;code&gt;up&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;down&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;suruseas/opossum/opossum
container system start                     &lt;span class="c"&gt;# start the runtime (once)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;container system dns create opossum   &lt;span class="c"&gt;# one-time: DNS domain so services&lt;/span&gt;
                                           &lt;span class="c"&gt;# resolve each other by name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Method: macOS 26 on Apple silicon, Apple &lt;code&gt;container&lt;/code&gt; 1.0. For each sample:&lt;br&gt;
&lt;code&gt;cd&lt;/code&gt; in, &lt;code&gt;opossum up&lt;/code&gt;, give it ~90 seconds to come up, check &lt;code&gt;ps&lt;/code&gt; and &lt;code&gt;logs&lt;/code&gt;,&lt;br&gt;
categorize, &lt;code&gt;down&lt;/code&gt;, next. Every failure got a second look to find the actual&lt;br&gt;
root cause — the interesting part is &lt;em&gt;whose fault&lt;/em&gt; each one was.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; container 1.1.0 shipped after the survey; it's a bug-fix release&lt;br&gt;
(UDS mounts, a &lt;code&gt;cp&lt;/code&gt; fix) and doesn't change these compatibility results. The&lt;br&gt;
Part 2 performance numbers were measured on 1.1.0.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  The results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ran &lt;strong&gt;as-is&lt;/strong&gt; — zero changes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;WordPress, React+Express+MySQL/Mongo, Spring, nginx+Go+MySQL, pgAdmin, Kafka, …&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ran after a &lt;strong&gt;one-line&lt;/strong&gt; compose change&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3× Postgres data dir, 1× amd64-only image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broken &lt;strong&gt;upstream&lt;/strong&gt; — the sample itself has rotted&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;would fail regardless of runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can't run &lt;strong&gt;by design&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Docker socket ×2, Linux kernel access ×1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment / setup&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;placeholder path, host port already taken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build outlasted my timeout&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;a 10–15 min Rust→Wasm build (no errors — I got impatient)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyz9tl96idoeca17kzw32.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyz9tl96idoeca17kzw32.png" alt="Results across the 29 awesome-compose samples" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The headline: &lt;strong&gt;18 of the 29 samples run on Apple's runtime today, 14 of&lt;br&gt;
them without touching a single line.&lt;/strong&gt; And of the 11 that didn't, &lt;em&gt;not one&lt;/em&gt;&lt;br&gt;
failed because of the runtime's container execution or networking — every&lt;br&gt;
failure traced to the sample itself, to Docker-specific host features, or to my&lt;br&gt;
environment.&lt;/p&gt;

&lt;p&gt;Let's look at the categories, because a couple of them teach you something&lt;br&gt;
about how Apple's runtime actually differs from Docker.&lt;/p&gt;
&lt;h3&gt;
  
  
  The one-line fixes (and what they reveal)
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Postgres refuses its data volume (3 samples)
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;nextcloud-postgres&lt;/code&gt;, &lt;code&gt;nginx-golang-postgres&lt;/code&gt;, and &lt;code&gt;spring-postgres&lt;/code&gt; all define&lt;br&gt;
the classic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db-data:/var/lib/postgresql/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Docker this works. On Apple &lt;code&gt;container&lt;/code&gt; Postgres dies with &lt;em&gt;"initdb: error:&lt;br&gt;
directory exists but is not empty"&lt;/em&gt;. Why: the runtime mounts a named volume as&lt;br&gt;
a real ext4 mount point — which contains &lt;code&gt;lost+found&lt;/code&gt; — and &lt;code&gt;initdb&lt;/code&gt; refuses a&lt;br&gt;
non-empty directory. Docker's volumes don't surface that detail.&lt;/p&gt;

&lt;p&gt;The one-line fix is the same one you'd use on any bare-metal mount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PGDATA&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/postgresql/data/pgdata&lt;/span&gt;   &lt;span class="c1"&gt;# a subdirectory of the mount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is &lt;em&gt;everywhere&lt;/em&gt; in self-hosted app composes (Gitea, Nextcloud,&lt;br&gt;
…), so opossum detects it at &lt;code&gt;up&lt;/code&gt; time and prints exactly that suggested fix.&lt;br&gt;
MySQL and MariaDB tolerate the mount point, which is why the WordPress and&lt;br&gt;
MySQL samples sailed through.&lt;/p&gt;
&lt;h4&gt;
  
  
  amd64-only images (1 sample)
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;nginx-nodejs-redis&lt;/code&gt; uses &lt;code&gt;redismod&lt;/code&gt;, an image published only for x86-64. The&lt;br&gt;
fix is the same line you'd add for Docker on Apple silicon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;opossum passes the platform through and automatically enables Rosetta&lt;br&gt;
translation for the container, so the x86-64 image runs on the arm64 VM.&lt;/p&gt;
&lt;h3&gt;
  
  
  The ones that were broken before I arrived (5 samples)
&lt;/h3&gt;

&lt;p&gt;This was the fun discovery. Five samples fail &lt;em&gt;on any runtime&lt;/em&gt; today, because&lt;br&gt;
they pin nothing and the world moved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nginx-flask-mysql&lt;/code&gt; — Flask backend crashes with &lt;code&gt;ImportError: cannot import
name 'url_quote' from 'werkzeug.urls'&lt;/code&gt;: the famous Flask/Werkzeug 2.1 break,
hit because the sample doesn't pin Werkzeug. (The nginx "host not found in
upstream" error that follows is just the dead backend cascading.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prometheus-grafana&lt;/code&gt; — the unpinned &lt;code&gt;prom/prometheus&lt;/code&gt; image now rejects the
sample's &lt;code&gt;api_version: v1&lt;/code&gt; Alertmanager config.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nginx-wsgi-flask&lt;/code&gt;, &lt;code&gt;react-rust-postgres&lt;/code&gt;, &lt;code&gt;vuejs&lt;/code&gt; — &lt;code&gt;pip install&lt;/code&gt; /
&lt;code&gt;cargo&lt;/code&gt; / &lt;code&gt;yarn global add&lt;/code&gt; failures during build, all dependency rot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I went in expecting to find runtime incompatibilities; instead I found a small&lt;br&gt;
museum of what happens to unmaintained compose files after a few years. If&lt;br&gt;
your own stack pins its images and dependencies, this whole category doesn't&lt;br&gt;
apply to you.&lt;/p&gt;
&lt;h3&gt;
  
  
  Can't run by design (3 samples)
&lt;/h3&gt;

&lt;p&gt;Full honesty, because this is where the model genuinely differs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;portainer&lt;/code&gt;, &lt;code&gt;traefik-golang&lt;/code&gt;&lt;/strong&gt; bind-mount &lt;code&gt;/var/run/docker.sock&lt;/code&gt; — they
manage/inspect &lt;em&gt;Docker&lt;/em&gt;. Apple's runtime has no Docker socket; anything whose
job is talking to the Docker daemon is out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;wireguard&lt;/code&gt;&lt;/strong&gt; needs &lt;code&gt;NET_ADMIN&lt;/code&gt; plus the host's kernel modules
(&lt;code&gt;/lib/modules&lt;/code&gt;). There's no shared Linux host kernel to reach into — each
container has its own micro-VM. (Docker Desktop on a Mac can't satisfy the
host-path part either.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your compose file is "a normal app + database + cache", none of this&lt;br&gt;
touches you. If it's host-level infrastructure tooling, stay on Docker.&lt;/p&gt;
&lt;h3&gt;
  
  
  The rest
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;plex&lt;/code&gt; ships a placeholder bind path (&lt;code&gt;/media/your/plex/path&lt;/code&gt;) you're meant
to edit — environment, not compatibility.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pihole-cloudflared-DoH&lt;/code&gt; wants host port 53, which my Mac was already using.
(opossum checks published ports up front and names the conflict instead of
failing mid-startup.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wasmedge-mysql-nginx&lt;/code&gt; compiles Rust to WebAssembly inside the build —
10–15 minutes of &lt;code&gt;cargo build&lt;/code&gt;. It was progressing without errors when my
90-second patience ran out, so it goes in the "almost certainly fine" pile,
not the failure pile.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Gotchas beyond this sample set
&lt;/h3&gt;

&lt;p&gt;Two more differences worth knowing before you try your own stack, found in&lt;br&gt;
wider testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fresh volumes mount empty.&lt;/strong&gt; Docker pre-fills a brand-new named/anonymous
volume from the image's contents at that path; Apple &lt;code&gt;container&lt;/code&gt; doesn't.
That breaks the beloved &lt;code&gt;- /app/node_modules&lt;/code&gt; trick. opossum replicates
Docker's seeding behavior itself, so this pattern works — but it's a real
runtime difference you'd hit with raw &lt;code&gt;container&lt;/code&gt; commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cgroup-sniffing JVMs crash.&lt;/strong&gt; Elasticsearch 7.x's bundled JDK reads the
host cgroup to size its heap, and the micro-VM doesn't expose the mount it
expects — it dies at launch before any config applies. A runtime/JDK
incompatibility with no workaround I know of.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A named volume can only be attached to one running container.&lt;/strong&gt; Apple
&lt;code&gt;container&lt;/code&gt; attaches a named volume as an exclusive block device to a single
VM; the second container that mounts the same volume fails with a cryptic
&lt;code&gt;VZErrorDomain … storage device attachment is invalid&lt;/code&gt;. Docker allows
sharing, and the common "app + nginx share an assets volume" pattern relies
on it — on Apple &lt;code&gt;container&lt;/code&gt;, use a bind mount (a host directory) for data
that two services must see at once. (opossum detects this case too and
warns at &lt;code&gt;up&lt;/code&gt; time.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Part 2 — Performance: this is where it's behind today
&lt;/h2&gt;

&lt;p&gt;Compatibility was the good news. Performance is the honest bad news, and it's&lt;br&gt;
the part you actually feel day to day. All numbers below are from one machine&lt;br&gt;
(M2, 16 GB; Apple &lt;code&gt;container&lt;/code&gt; 1.1.0, Docker Desktop / Engine 29.5.3) —&lt;br&gt;
reproduce them yourself; the point is the &lt;em&gt;shape&lt;/em&gt;, not the third decimal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory doesn't work the way the idle number suggests.&lt;/strong&gt; The headline "~50 MB&lt;br&gt;
idle vs Docker's multi-GB VM" is true, but it's only the start of the story,&lt;br&gt;
because the two runtimes allocate memory completely differently. Apple &lt;code&gt;container&lt;/code&gt; runs &lt;strong&gt;one VM&lt;br&gt;
per container&lt;/strong&gt;, and that cost is exact and verifiable: an idle &lt;code&gt;nginx:alpine&lt;/code&gt;&lt;br&gt;
VM has a &lt;strong&gt;~270 MB&lt;/strong&gt; physical footprint (a bare alpine idles at ~235–255 MB —&lt;br&gt;
that's the fixed guest-kernel floor, and lowering the &lt;code&gt;-m&lt;/code&gt; cap doesn't lower&lt;br&gt;
it), it scales dead linearly (six containers = six VMs ≈ 1.6 GB + ~20 MB of&lt;br&gt;
helpers each), and macOS genuinely attributes the guest's memory to the VM&lt;br&gt;
process — I held 300 MB of incompressible data inside one and watched its&lt;br&gt;
footprint grow by exactly that (254 M → 557 M).&lt;/p&gt;

&lt;p&gt;Docker runs &lt;strong&gt;one shared VM&lt;/strong&gt; all containers draw from — and measuring it&lt;br&gt;
honestly has a trap I fell into myself: Docker's guest memory doesn't show up&lt;br&gt;
under any &lt;code&gt;com.docker.*&lt;/code&gt; process. It lives in a process Activity Monitor calls&lt;br&gt;
&lt;strong&gt;"Virtual Machine Service for Docker"&lt;/strong&gt; (both runtimes use Apple's&lt;br&gt;
Virtualization framework), and the same 300 MB test grows &lt;em&gt;that&lt;/em&gt; process by&lt;br&gt;
exactly 0.3 GB (1.1 G → 1.4 G). Sum it correctly and Docker looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Apple &lt;code&gt;container&lt;/code&gt; (exact, linear)&lt;/th&gt;
&lt;th&gt;Docker Desktop (elastic base)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nothing running&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~50 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~1.4–2.1 GB warm (helpers + VM; shrinks only over long idle)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Each added small container&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+~290 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~&lt;strong&gt;+0&lt;/strong&gt; (six more nginx moved it ~10 MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heavy workload ends&lt;/td&gt;
&lt;td&gt;VM freed with the container&lt;/td&gt;
&lt;td&gt;VM &lt;strong&gt;keeps&lt;/strong&gt; its high-water mark&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqz2a6to2m7chkv7btoy6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqz2a6to2m7chkv7btoy6.png" alt="Real memory vs. running containers: Apple container grows linearly, Docker stays roughly flat within a state-dependent range" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the honest memory verdict is subtler than either camp's pitch: Apple wins&lt;br&gt;
clearly when idle or running 1–2 things; &lt;strong&gt;at a typical 5–10 service stack the&lt;br&gt;
totals are comparable&lt;/strong&gt; (Apple ~1.5–3 GB of exact, linear cost vs Docker's&lt;br&gt;
~1.4–2.1 GB elastic base); past ~10 services Docker's shared pool wins. The&lt;br&gt;
crossover lands anywhere from ~2 to ~7 containers depending on how warm&lt;br&gt;
Docker's VM is. The per-VM model's real upside is isolation (a runaway&lt;br&gt;
container can't starve its neighbors) and &lt;em&gt;predictability&lt;/em&gt; — you can point at&lt;br&gt;
the exact process each container costs you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually decides the dev-stack question is speed, not memory.&lt;/strong&gt; Same&lt;br&gt;
machine:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Docker Desktop&lt;/th&gt;
&lt;th&gt;Apple &lt;code&gt;container&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single container start&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0.19 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~0.81 s (boots a fresh micro-VM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10× &lt;code&gt;run --rm&lt;/code&gt;, sequential&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.1 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8.3 s (~4× slower)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10× &lt;code&gt;run --rm&lt;/code&gt;, in parallel&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.75 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.6 s (~10× — a shared daemon parallelizes, per-VM doesn't)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First build in a session&lt;/td&gt;
&lt;td&gt;warm&lt;/td&gt;
&lt;td&gt;+~6 s builder-VM cold start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bind-mount small-file I/O&lt;/td&gt;
&lt;td&gt;slow&lt;/td&gt;
&lt;td&gt;slightly &lt;strong&gt;slower&lt;/strong&gt; (same host↔VM model)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The throwaway-container gap is architectural — one lightweight VM per container —&lt;br&gt;
so it won't simply close with a point release. For anything that churns&lt;br&gt;
short-lived containers (test suites, CI-like loops) it's a real tax today.&lt;/p&gt;

&lt;p&gt;The pieces are all there in Apple &lt;code&gt;container&lt;/code&gt;. But on this performance profile,&lt;br&gt;
a daily multi-service dev stack is &lt;strong&gt;not where it wins yet&lt;/strong&gt; — that's the&lt;br&gt;
Part 2 conclusion. (&lt;a href="https://github.com/suruseas/opossum/blob/main/docs/vs-docker-desktop.md" rel="noopener noreferrer"&gt;Full method and the wider comparison&lt;/a&gt;.)&lt;/p&gt;
&lt;h2&gt;
  
  
  The verdict: when to pick it (and when not to, yet)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your situation&lt;/th&gt;
&lt;th&gt;Today's pick&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Daily multi-service dev stack (app + DB + cache + …)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Docker / Colima / OrbStack&lt;/strong&gt; — faster where it counts (memory is roughly comparable at this scale)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Churning lots of short-lived containers (tests)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Docker&lt;/strong&gt; — per-VM start cost hurts here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needs &lt;code&gt;docker.sock&lt;/code&gt;, &lt;code&gt;NET_ADMIN&lt;/code&gt;, host kernel&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Docker&lt;/strong&gt; — Apple &lt;code&gt;container&lt;/code&gt; can't, by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A small image or two, run occasionally, mostly idle&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Apple &lt;code&gt;container&lt;/code&gt;&lt;/strong&gt; — genuinely lighter, cleaner isolation, no license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want per-container VM isolation and no always-on VM&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Apple &lt;code&gt;container&lt;/code&gt;&lt;/strong&gt; — the one thing nothing else here gives you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's the honest 2026 read: &lt;strong&gt;the 1.0 pieces are in place, compatibility is&lt;br&gt;
real, but the performance envelope means Docker and the established alternatives&lt;br&gt;
are the practical choice for a normal dev environment right now.&lt;/strong&gt; Apple&lt;br&gt;
&lt;code&gt;container&lt;/code&gt;'s current sweet spot is narrow — small, occasional, idle-most-of-the-&lt;br&gt;
time workloads — and its structural advantage (VM-per-container isolation with no&lt;br&gt;
resident VM) is worth watching as the runtime matures. I'd revisit this in a few&lt;br&gt;
releases; I wouldn't move my main stack over today.&lt;/p&gt;
&lt;h2&gt;
  
  
  So if not docker compose, what is it for?
&lt;/h2&gt;

&lt;p&gt;"Not yet for your main dev stack" sounds like a verdict with no upside, but&lt;br&gt;
flip the measured properties around and a clear niche appears: near-zero cost&lt;br&gt;
at idle, a VM boundary around every container, ~1s to start one. In other&lt;br&gt;
words, &lt;strong&gt;small things, run occasionally, isolated.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The toolbox for coding agents (MCP servers).&lt;/strong&gt; The MCP servers you wire
into Claude Code and friends are a handful of small images that run only
during a session and idle the rest of the time — and they're third-party
code holding your API tokens, so per-VM isolation is a real benefit. On my
machine, &lt;code&gt;claude mcp add terraform -- container run -i --rm
hashicorp/terraform-mcp-server&lt;/code&gt; is the whole setup, and it works with Docker
Desktop not even installed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A sandbox for AI agents.&lt;/strong&gt; For running agent-generated code
autonomously, a disposable VM boundary is one step stronger than a
kernel-sharing container. Line up WWDC26's announcements — MCP support in
Xcode 27, Container machines — and Apple itself seems to be looking in this
direction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll cover the verification and setup for these in follow-up posts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it yourself — it's safe next to Docker
&lt;/h2&gt;

&lt;p&gt;If you want to see where your own compose lands, it's three commands, and it&lt;br&gt;
won't touch your Docker state (Apple's runtime keeps entirely separate images,&lt;br&gt;
containers, and volumes; &lt;code&gt;opossum down -v&lt;/code&gt; only ever removes its own):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;suruseas/opossum/opossum
container system start                     &lt;span class="c"&gt;# start the runtime&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;container system dns create opossum   &lt;span class="c"&gt;# once: DNS domain for name resolution&lt;/span&gt;

&lt;span class="nb"&gt;cd &lt;/span&gt;your-project        &lt;span class="c"&gt;# with its existing docker-compose.yml&lt;/span&gt;
opossum config         &lt;span class="c"&gt;# optional: preview which fields (if any) get ignored&lt;/span&gt;
opossum up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;docker compose&lt;/code&gt; already built your images, &lt;code&gt;opossum up --from-docker&lt;/code&gt;&lt;br&gt;
imports them and starts without rebuilding — it changes the first-run&lt;br&gt;
experience considerably. And if something misbehaves, &lt;code&gt;opossum doctor&lt;/code&gt;&lt;br&gt;
diagnoses the environment (runtime, DNS, network, builder) and tells you the&lt;br&gt;
fix.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;https://github.com/suruseas/opossum&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing notes
&lt;/h2&gt;

&lt;p&gt;I'll keep expanding this investigation of Apple &lt;code&gt;container&lt;/code&gt;. If you re-run the&lt;br&gt;
performance numbers on different hardware and get different results, I'd love&lt;br&gt;
to hear about it. And if you point &lt;code&gt;opossum up&lt;/code&gt; at a real compose file and&lt;br&gt;
something breaks, please tell me — in the comments or in an&lt;br&gt;
&lt;a href="https://github.com/suruseas/opossum/issues" rel="noopener noreferrer"&gt;issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is also available in Japanese: &lt;a href="https://zenn.dev/suruseas/articles/93aec16eef8792" rel="noopener noreferrer"&gt;Zenn version&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>macos</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>I ran 29 real docker-compose projects on Apple's container runtime. Here's what broke</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Tue, 07 Jul 2026 14:51:59 +0000</pubDate>
      <link>https://dev.to/suruseas/i-ran-29-real-docker-compose-projects-on-apples-container-runtime-heres-what-broke-2fk7</link>
      <guid>https://dev.to/suruseas/i-ran-29-real-docker-compose-projects-on-apples-container-runtime-heres-what-broke-2fk7</guid>
      <description>&lt;p&gt;Docker Desktop keeps a &lt;strong&gt;~7.8 GB Linux VM&lt;/strong&gt; provisioned the entire time it's&lt;br&gt;
running — even when zero containers are up. Apple's new&lt;br&gt;
&lt;a href="https://github.com/apple/container" rel="noopener noreferrer"&gt;&lt;code&gt;container&lt;/code&gt;&lt;/a&gt; runtime idles at &lt;strong&gt;~58 MB&lt;/strong&gt; of&lt;br&gt;
helper processes and only spends memory while a container actually runs, because&lt;br&gt;
it boots a lightweight VM &lt;em&gt;per container&lt;/em&gt;, on demand. It hit 1.0 in June, and on&lt;br&gt;
macOS 26 containers can finally talk to each other, which makes multi-service&lt;br&gt;
dev stacks possible.&lt;/p&gt;

&lt;p&gt;So the question stopped being "is it interesting?" and became: &lt;strong&gt;is it&lt;br&gt;
compatible enough to run the &lt;code&gt;docker-compose.yml&lt;/code&gt; files people actually have?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of guessing, I measured. I took Docker's own&lt;br&gt;
&lt;a href="https://github.com/docker/awesome-compose" rel="noopener noreferrer"&gt;awesome-compose&lt;/a&gt; repository — the&lt;br&gt;
official collection of sample compose projects (WordPress, React+Express+Mongo,&lt;br&gt;
Spring+Postgres, Prometheus+Grafana, …) — and ran &lt;strong&gt;29 of its samples,&lt;br&gt;
unmodified&lt;/strong&gt;, on Apple &lt;code&gt;container&lt;/code&gt;. An alphabetical slice, no cherry-picking:&lt;br&gt;
every sample from &lt;code&gt;nextcloud-postgres&lt;/code&gt; through &lt;code&gt;wordpress-mysql&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the breakdown, and — more useful — &lt;em&gt;why&lt;/em&gt; each failure failed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Apple's &lt;code&gt;container&lt;/code&gt; is a runtime, not an orchestrator: it has no &lt;code&gt;compose&lt;/code&gt;&lt;br&gt;
subcommand, no dependency ordering, no service discovery. For that I used&lt;br&gt;
&lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;opossum&lt;/a&gt;, a small compose-like&lt;br&gt;
orchestrator for Apple &lt;code&gt;container&lt;/code&gt; that I've been building. It reads your&lt;br&gt;
existing &lt;code&gt;compose.yaml&lt;/code&gt; / &lt;code&gt;docker-compose.yml&lt;/code&gt; and gives you the familiar&lt;br&gt;
verbs — &lt;code&gt;up&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;down&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;suruseas/opossum/opossum
container system start                     &lt;span class="c"&gt;# start the runtime (once)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;container system dns create opossum   &lt;span class="c"&gt;# one-time: DNS domain so services&lt;/span&gt;
                                           &lt;span class="c"&gt;# resolve each other by name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Method: macOS 26 on Apple silicon, Apple &lt;code&gt;container&lt;/code&gt; 1.0. For each sample:&lt;br&gt;
&lt;code&gt;cd&lt;/code&gt; in, &lt;code&gt;opossum up&lt;/code&gt;, give it ~90 seconds to come up, check &lt;code&gt;ps&lt;/code&gt; and &lt;code&gt;logs&lt;/code&gt;,&lt;br&gt;
categorize, &lt;code&gt;down&lt;/code&gt;, next. Every failure got a second look to find the actual&lt;br&gt;
root cause — the interesting part is &lt;em&gt;whose fault&lt;/em&gt; each one was.&lt;/p&gt;
&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;✅ Ran &lt;strong&gt;as-is&lt;/strong&gt; — zero changes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;WordPress, React+Express+MySQL/Mongo, Spring, nginx+Go+MySQL, pgAdmin, Kafka, …&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔧 Ran after a &lt;strong&gt;one-line&lt;/strong&gt; compose change&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3× Postgres data dir, 1× amd64-only image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🏗️ Broken &lt;strong&gt;upstream&lt;/strong&gt; — the sample itself has rotted&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;would fail regardless of runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🚫 Can't run &lt;strong&gt;by design&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Docker socket ×2, Linux kernel access ×1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚙️ Environment / setup&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;placeholder path, host port already taken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🐌 Build outlasted my timeout&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;a 10–15 min Rust→Wasm build (no errors — I got impatient)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The headline: &lt;strong&gt;18 of 29 real-world stacks run on Apple's runtime today, 14 of&lt;br&gt;
them without touching a single line.&lt;/strong&gt; And of the 11 that didn't, &lt;em&gt;not one&lt;/em&gt;&lt;br&gt;
failed because of the runtime's container execution or networking — every&lt;br&gt;
failure traced to the sample itself, to Docker-specific host features, or to my&lt;br&gt;
environment.&lt;/p&gt;

&lt;p&gt;Let's look at the categories, because a couple of them teach you something&lt;br&gt;
about how Apple's runtime actually differs from Docker.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one-line fixes (and what they reveal)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Postgres refuses its data volume (3 samples)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;nextcloud-postgres&lt;/code&gt;, &lt;code&gt;nginx-golang-postgres&lt;/code&gt;, and &lt;code&gt;spring-postgres&lt;/code&gt; all define&lt;br&gt;
the classic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db-data:/var/lib/postgresql/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Docker this works. On Apple &lt;code&gt;container&lt;/code&gt; Postgres dies with &lt;em&gt;"initdb: error:&lt;br&gt;
directory exists but is not empty"&lt;/em&gt;. Why: the runtime mounts a named volume as&lt;br&gt;
a real ext4 mount point — which contains &lt;code&gt;lost+found&lt;/code&gt; — and &lt;code&gt;initdb&lt;/code&gt; refuses a&lt;br&gt;
non-empty directory. Docker's volumes don't surface that detail.&lt;/p&gt;

&lt;p&gt;The one-line fix is the same one you'd use on any bare-metal mount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PGDATA&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/postgresql/data/pgdata&lt;/span&gt;   &lt;span class="c1"&gt;# a subdirectory of the mount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is &lt;em&gt;everywhere&lt;/em&gt; in self-hosted app composes (Gitea, Nextcloud,&lt;br&gt;
…), so opossum detects it at &lt;code&gt;up&lt;/code&gt; time and prints exactly that suggested fix.&lt;br&gt;
MySQL and MariaDB tolerate the mount point, which is why the WordPress and&lt;br&gt;
MySQL samples sailed through.&lt;/p&gt;
&lt;h3&gt;
  
  
  amd64-only images (1 sample)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;nginx-nodejs-redis&lt;/code&gt; uses &lt;code&gt;redismod&lt;/code&gt;, an image published only for x86-64. The&lt;br&gt;
fix is the same line you'd add for Docker on Apple silicon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;opossum passes the platform through and automatically enables Rosetta&lt;br&gt;
translation for the container, so the x86-64 image runs on the arm64 VM.&lt;/p&gt;
&lt;h2&gt;
  
  
  The ones that were broken before I arrived (5 samples)
&lt;/h2&gt;

&lt;p&gt;This was the fun discovery. Five samples fail &lt;em&gt;on any runtime&lt;/em&gt; today, because&lt;br&gt;
they pin nothing and the world moved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nginx-flask-mysql&lt;/code&gt; — Flask backend crashes with &lt;code&gt;ImportError: cannot import
name 'url_quote' from 'werkzeug.urls'&lt;/code&gt;: the famous Flask/Werkzeug 2.1 break,
hit because the sample doesn't pin Werkzeug. (The nginx "host not found in
upstream" error that follows is just the dead backend cascading.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prometheus-grafana&lt;/code&gt; — the unpinned &lt;code&gt;prom/prometheus&lt;/code&gt; image now rejects the
sample's &lt;code&gt;api_version: v1&lt;/code&gt; Alertmanager config.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nginx-wsgi-flask&lt;/code&gt;, &lt;code&gt;react-rust-postgres&lt;/code&gt;, &lt;code&gt;vuejs&lt;/code&gt; — &lt;code&gt;pip install&lt;/code&gt; /
&lt;code&gt;cargo&lt;/code&gt; / &lt;code&gt;yarn global add&lt;/code&gt; failures during build, all dependency rot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I went in expecting to find runtime incompatibilities; instead I found a small&lt;br&gt;
museum of what happens to unmaintained compose files after a few years. If&lt;br&gt;
your own stack pins its images and dependencies, this whole category doesn't&lt;br&gt;
apply to you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Can't run by design (3 samples)
&lt;/h2&gt;

&lt;p&gt;Full honesty, because this is where the model genuinely differs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;portainer&lt;/code&gt;, &lt;code&gt;traefik-golang&lt;/code&gt;&lt;/strong&gt; bind-mount &lt;code&gt;/var/run/docker.sock&lt;/code&gt; — they
manage/inspect &lt;em&gt;Docker&lt;/em&gt;. Apple's runtime has no Docker socket; anything whose
job is talking to the Docker daemon is out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;wireguard&lt;/code&gt;&lt;/strong&gt; needs &lt;code&gt;NET_ADMIN&lt;/code&gt; plus the host's kernel modules
(&lt;code&gt;/lib/modules&lt;/code&gt;). There's no shared Linux host kernel to reach into — each
container has its own micro-VM. (Docker Desktop on a Mac can't satisfy the
host-path part either.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your compose file is "a normal app + database + cache", none of this&lt;br&gt;
touches you. If it's host-level infrastructure tooling, stay on Docker.&lt;/p&gt;
&lt;h2&gt;
  
  
  The rest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;plex&lt;/code&gt; ships a placeholder bind path (&lt;code&gt;/media/your/plex/path&lt;/code&gt;) you're meant
to edit — environment, not compatibility.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pihole-cloudflared-DoH&lt;/code&gt; wants host port 53, which my Mac was already using.
(opossum checks published ports up front and names the conflict instead of
failing mid-startup.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wasmedge-mysql-nginx&lt;/code&gt; compiles Rust to WebAssembly inside the build —
10–15 minutes of &lt;code&gt;cargo build&lt;/code&gt;. It was progressing without errors when my
90-second patience ran out, so it goes in the "almost certainly fine" pile,
not the failure pile.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Gotchas beyond this sample set
&lt;/h2&gt;

&lt;p&gt;Two more differences worth knowing before you try your own stack, found in&lt;br&gt;
wider testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fresh volumes mount empty.&lt;/strong&gt; Docker pre-fills a brand-new named/anonymous
volume from the image's contents at that path; Apple &lt;code&gt;container&lt;/code&gt; doesn't.
That breaks the beloved &lt;code&gt;- /app/node_modules&lt;/code&gt; trick. opossum replicates
Docker's seeding behavior itself, so this pattern works — but it's a real
runtime difference you'd hit with raw &lt;code&gt;container&lt;/code&gt; commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cgroup-sniffing JVMs crash.&lt;/strong&gt; Elasticsearch 7.x's bundled JDK reads the
host cgroup to size its heap, and the micro-VM doesn't expose the mount it
expects — it dies at launch before any config applies. A runtime/JDK
incompatibility with no workaround I know of.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the trade-offs cut both ways — this isn't "Docker but better":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Docker Desktop&lt;/th&gt;
&lt;th&gt;Apple &lt;code&gt;container&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Idle memory&lt;/td&gt;
&lt;td&gt;~373 MB host procs + &lt;strong&gt;~7.8 GB VM&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~58 MB&lt;/strong&gt;, no always-on VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single container start&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0.19 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~0.81 s (boots a fresh micro-VM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bind-mount small-file I/O&lt;/td&gt;
&lt;td&gt;slow&lt;/td&gt;
&lt;td&gt;slightly &lt;strong&gt;slower&lt;/strong&gt; (same host↔VM model)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolation&lt;/td&gt;
&lt;td&gt;shared VM kernel&lt;/td&gt;
&lt;td&gt;per-container VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;paid for larger orgs&lt;/td&gt;
&lt;td&gt;open source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Docker still wins if you churn many short-lived containers or hammer bind&lt;br&gt;
mounts. Apple's runtime wins on footprint and isolation. (Numbers from one&lt;br&gt;
machine — &lt;a href="https://github.com/suruseas/opossum/blob/main/docs/benchmarks.md" rel="noopener noreferrer"&gt;method and caveats here&lt;/a&gt;.)&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it on your own compose file
&lt;/h2&gt;

&lt;p&gt;The whole experiment, reproduced on your project, is three commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;suruseas/opossum/opossum
&lt;span class="nb"&gt;cd &lt;/span&gt;your-project        &lt;span class="c"&gt;# with its existing docker-compose.yml&lt;/span&gt;
opossum up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's safe to try next to Docker: Apple's runtime keeps entirely separate&lt;br&gt;
images, containers, and volumes, so your Docker state isn't touched. &lt;code&gt;opossum&lt;br&gt;
config&lt;/code&gt; shows you up front which compose fields (if any) it will ignore, and&lt;br&gt;
&lt;code&gt;down&lt;/code&gt; cleans everything up.&lt;/p&gt;

&lt;p&gt;Based on this sample: &lt;strong&gt;48% of real-world stacks ran with zero changes, 62%&lt;br&gt;
with at most one line&lt;/strong&gt; — and the failures concentrated in stale samples and&lt;br&gt;
Docker-specific tooling, not in the runtime.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;https://github.com/suruseas/opossum&lt;/a&gt; — and I'd genuinely like to grow&lt;br&gt;
this survey: if you run &lt;code&gt;opossum up&lt;/code&gt; on a real compose file and it breaks,&lt;br&gt;
tell me what broke (here or in an issue). The failure catalog is the product.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>macos</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>I ran 29 real docker-compose projects on Apple's container runtime. Here's what broke</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Tue, 07 Jul 2026 14:47:11 +0000</pubDate>
      <link>https://dev.to/suruseas/i-ran-29-real-docker-compose-projects-on-apples-container-runtime-heres-what-broke-28cb</link>
      <guid>https://dev.to/suruseas/i-ran-29-real-docker-compose-projects-on-apples-container-runtime-heres-what-broke-28cb</guid>
      <description>&lt;p&gt;Docker Desktop keeps a &lt;strong&gt;~7.8 GB Linux VM&lt;/strong&gt; provisioned the entire time it's&lt;br&gt;
running — even when zero containers are up. Apple's new&lt;br&gt;
&lt;a href="https://github.com/apple/container" rel="noopener noreferrer"&gt;&lt;code&gt;container&lt;/code&gt;&lt;/a&gt; runtime idles at &lt;strong&gt;~58 MB&lt;/strong&gt; of&lt;br&gt;
helper processes and only spends memory while a container actually runs, because&lt;br&gt;
it boots a lightweight VM &lt;em&gt;per container&lt;/em&gt;, on demand. It hit 1.0 in June, and on&lt;br&gt;
macOS 26 containers can finally talk to each other, which makes multi-service&lt;br&gt;
dev stacks possible.&lt;/p&gt;

&lt;p&gt;So the question stopped being "is it interesting?" and became: &lt;strong&gt;is it&lt;br&gt;
compatible enough to run the &lt;code&gt;docker-compose.yml&lt;/code&gt; files people actually have?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of guessing, I measured. I took Docker's own&lt;br&gt;
&lt;a href="https://github.com/docker/awesome-compose" rel="noopener noreferrer"&gt;awesome-compose&lt;/a&gt; repository — the&lt;br&gt;
official collection of sample compose projects (WordPress, React+Express+Mongo,&lt;br&gt;
Spring+Postgres, Prometheus+Grafana, …) — and ran &lt;strong&gt;29 of its samples,&lt;br&gt;
unmodified&lt;/strong&gt;, on Apple &lt;code&gt;container&lt;/code&gt;. An alphabetical slice, no cherry-picking:&lt;br&gt;
every sample from &lt;code&gt;nextcloud-postgres&lt;/code&gt; through &lt;code&gt;wordpress-mysql&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the breakdown, and — more useful — &lt;em&gt;why&lt;/em&gt; each failure failed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Apple's &lt;code&gt;container&lt;/code&gt; is a runtime, not an orchestrator: it has no &lt;code&gt;compose&lt;/code&gt;&lt;br&gt;
subcommand, no dependency ordering, no service discovery. For that I used&lt;br&gt;
&lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;opossum&lt;/a&gt;, a small compose-like&lt;br&gt;
orchestrator for Apple &lt;code&gt;container&lt;/code&gt; that I've been building. It reads your&lt;br&gt;
existing &lt;code&gt;compose.yaml&lt;/code&gt; / &lt;code&gt;docker-compose.yml&lt;/code&gt; and gives you the familiar&lt;br&gt;
verbs — &lt;code&gt;up&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;down&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;suruseas/opossum/opossum
container system start                     &lt;span class="c"&gt;# start the runtime (once)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;container system dns create opossum   &lt;span class="c"&gt;# one-time: DNS domain so services&lt;/span&gt;
                                           &lt;span class="c"&gt;# resolve each other by name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Method: macOS 26 on Apple silicon, Apple &lt;code&gt;container&lt;/code&gt; 1.0. For each sample:&lt;br&gt;
&lt;code&gt;cd&lt;/code&gt; in, &lt;code&gt;opossum up&lt;/code&gt;, give it ~90 seconds to come up, check &lt;code&gt;ps&lt;/code&gt; and &lt;code&gt;logs&lt;/code&gt;,&lt;br&gt;
categorize, &lt;code&gt;down&lt;/code&gt;, next. Every failure got a second look to find the actual&lt;br&gt;
root cause — the interesting part is &lt;em&gt;whose fault&lt;/em&gt; each one was.&lt;/p&gt;
&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;✅ Ran &lt;strong&gt;as-is&lt;/strong&gt; — zero changes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;WordPress, React+Express+MySQL/Mongo, Spring, nginx+Go+MySQL, pgAdmin, Kafka, …&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔧 Ran after a &lt;strong&gt;one-line&lt;/strong&gt; compose change&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3× Postgres data dir, 1× amd64-only image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🏗️ Broken &lt;strong&gt;upstream&lt;/strong&gt; — the sample itself has rotted&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;would fail regardless of runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🚫 Can't run &lt;strong&gt;by design&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Docker socket ×2, Linux kernel access ×1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚙️ Environment / setup&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;placeholder path, host port already taken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🐌 Build outlasted my timeout&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;a 10–15 min Rust→Wasm build (no errors — I got impatient)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The headline: &lt;strong&gt;18 of 29 real-world stacks run on Apple's runtime today, 14 of&lt;br&gt;
them without touching a single line.&lt;/strong&gt; And of the 11 that didn't, &lt;em&gt;not one&lt;/em&gt;&lt;br&gt;
failed because of the runtime's container execution or networking — every&lt;br&gt;
failure traced to the sample itself, to Docker-specific host features, or to my&lt;br&gt;
environment.&lt;/p&gt;

&lt;p&gt;Let's look at the categories, because a couple of them teach you something&lt;br&gt;
about how Apple's runtime actually differs from Docker.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one-line fixes (and what they reveal)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Postgres refuses its data volume (3 samples)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;nextcloud-postgres&lt;/code&gt;, &lt;code&gt;nginx-golang-postgres&lt;/code&gt;, and &lt;code&gt;spring-postgres&lt;/code&gt; all define&lt;br&gt;
the classic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db-data:/var/lib/postgresql/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Docker this works. On Apple &lt;code&gt;container&lt;/code&gt; Postgres dies with &lt;em&gt;"initdb: error:&lt;br&gt;
directory exists but is not empty"&lt;/em&gt;. Why: the runtime mounts a named volume as&lt;br&gt;
a real ext4 mount point — which contains &lt;code&gt;lost+found&lt;/code&gt; — and &lt;code&gt;initdb&lt;/code&gt; refuses a&lt;br&gt;
non-empty directory. Docker's volumes don't surface that detail.&lt;/p&gt;

&lt;p&gt;The one-line fix is the same one you'd use on any bare-metal mount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PGDATA&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/postgresql/data/pgdata&lt;/span&gt;   &lt;span class="c1"&gt;# a subdirectory of the mount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is &lt;em&gt;everywhere&lt;/em&gt; in self-hosted app composes (Gitea, Nextcloud,&lt;br&gt;
…), so opossum detects it at &lt;code&gt;up&lt;/code&gt; time and prints exactly that suggested fix.&lt;br&gt;
MySQL and MariaDB tolerate the mount point, which is why the WordPress and&lt;br&gt;
MySQL samples sailed through.&lt;/p&gt;
&lt;h3&gt;
  
  
  amd64-only images (1 sample)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;nginx-nodejs-redis&lt;/code&gt; uses &lt;code&gt;redismod&lt;/code&gt;, an image published only for x86-64. The&lt;br&gt;
fix is the same line you'd add for Docker on Apple silicon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;opossum passes the platform through and automatically enables Rosetta&lt;br&gt;
translation for the container, so the x86-64 image runs on the arm64 VM.&lt;/p&gt;
&lt;h2&gt;
  
  
  The ones that were broken before I arrived (5 samples)
&lt;/h2&gt;

&lt;p&gt;This was the fun discovery. Five samples fail &lt;em&gt;on any runtime&lt;/em&gt; today, because&lt;br&gt;
they pin nothing and the world moved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nginx-flask-mysql&lt;/code&gt; — Flask backend crashes with &lt;code&gt;ImportError: cannot import
name 'url_quote' from 'werkzeug.urls'&lt;/code&gt;: the famous Flask/Werkzeug 2.1 break,
hit because the sample doesn't pin Werkzeug. (The nginx "host not found in
upstream" error that follows is just the dead backend cascading.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prometheus-grafana&lt;/code&gt; — the unpinned &lt;code&gt;prom/prometheus&lt;/code&gt; image now rejects the
sample's &lt;code&gt;api_version: v1&lt;/code&gt; Alertmanager config.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nginx-wsgi-flask&lt;/code&gt;, &lt;code&gt;react-rust-postgres&lt;/code&gt;, &lt;code&gt;vuejs&lt;/code&gt; — &lt;code&gt;pip install&lt;/code&gt; /
&lt;code&gt;cargo&lt;/code&gt; / &lt;code&gt;yarn global add&lt;/code&gt; failures during build, all dependency rot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I went in expecting to find runtime incompatibilities; instead I found a small&lt;br&gt;
museum of what happens to unmaintained compose files after a few years. If&lt;br&gt;
your own stack pins its images and dependencies, this whole category doesn't&lt;br&gt;
apply to you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Can't run by design (3 samples)
&lt;/h2&gt;

&lt;p&gt;Full honesty, because this is where the model genuinely differs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;portainer&lt;/code&gt;, &lt;code&gt;traefik-golang&lt;/code&gt;&lt;/strong&gt; bind-mount &lt;code&gt;/var/run/docker.sock&lt;/code&gt; — they
manage/inspect &lt;em&gt;Docker&lt;/em&gt;. Apple's runtime has no Docker socket; anything whose
job is talking to the Docker daemon is out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;wireguard&lt;/code&gt;&lt;/strong&gt; needs &lt;code&gt;NET_ADMIN&lt;/code&gt; plus the host's kernel modules
(&lt;code&gt;/lib/modules&lt;/code&gt;). There's no shared Linux host kernel to reach into — each
container has its own micro-VM. (Docker Desktop on a Mac can't satisfy the
host-path part either.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your compose file is "a normal app + database + cache", none of this&lt;br&gt;
touches you. If it's host-level infrastructure tooling, stay on Docker.&lt;/p&gt;
&lt;h2&gt;
  
  
  The rest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;plex&lt;/code&gt; ships a placeholder bind path (&lt;code&gt;/media/your/plex/path&lt;/code&gt;) you're meant
to edit — environment, not compatibility.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pihole-cloudflared-DoH&lt;/code&gt; wants host port 53, which my Mac was already using.
(opossum checks published ports up front and names the conflict instead of
failing mid-startup.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wasmedge-mysql-nginx&lt;/code&gt; compiles Rust to WebAssembly inside the build —
10–15 minutes of &lt;code&gt;cargo build&lt;/code&gt;. It was progressing without errors when my
90-second patience ran out, so it goes in the "almost certainly fine" pile,
not the failure pile.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Gotchas beyond this sample set
&lt;/h2&gt;

&lt;p&gt;Two more differences worth knowing before you try your own stack, found in&lt;br&gt;
wider testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fresh volumes mount empty.&lt;/strong&gt; Docker pre-fills a brand-new named/anonymous
volume from the image's contents at that path; Apple &lt;code&gt;container&lt;/code&gt; doesn't.
That breaks the beloved &lt;code&gt;- /app/node_modules&lt;/code&gt; trick. opossum replicates
Docker's seeding behavior itself, so this pattern works — but it's a real
runtime difference you'd hit with raw &lt;code&gt;container&lt;/code&gt; commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cgroup-sniffing JVMs crash.&lt;/strong&gt; Elasticsearch 7.x's bundled JDK reads the
host cgroup to size its heap, and the micro-VM doesn't expose the mount it
expects — it dies at launch before any config applies. A runtime/JDK
incompatibility with no workaround I know of.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the trade-offs cut both ways — this isn't "Docker but better":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Docker Desktop&lt;/th&gt;
&lt;th&gt;Apple &lt;code&gt;container&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Idle memory&lt;/td&gt;
&lt;td&gt;~373 MB host procs + &lt;strong&gt;~7.8 GB VM&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~58 MB&lt;/strong&gt;, no always-on VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single container start&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0.19 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~0.81 s (boots a fresh micro-VM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bind-mount small-file I/O&lt;/td&gt;
&lt;td&gt;slow&lt;/td&gt;
&lt;td&gt;slightly &lt;strong&gt;slower&lt;/strong&gt; (same host↔VM model)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolation&lt;/td&gt;
&lt;td&gt;shared VM kernel&lt;/td&gt;
&lt;td&gt;per-container VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;paid for larger orgs&lt;/td&gt;
&lt;td&gt;open source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Docker still wins if you churn many short-lived containers or hammer bind&lt;br&gt;
mounts. Apple's runtime wins on footprint and isolation. (Numbers from one&lt;br&gt;
machine — &lt;a href="https://github.com/suruseas/opossum/blob/main/docs/benchmarks.md" rel="noopener noreferrer"&gt;method and caveats here&lt;/a&gt;.)&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it on your own compose file
&lt;/h2&gt;

&lt;p&gt;The whole experiment, reproduced on your project, is three commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;suruseas/opossum/opossum
&lt;span class="nb"&gt;cd &lt;/span&gt;your-project        &lt;span class="c"&gt;# with its existing docker-compose.yml&lt;/span&gt;
opossum up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's safe to try next to Docker: Apple's runtime keeps entirely separate&lt;br&gt;
images, containers, and volumes, so your Docker state isn't touched. &lt;code&gt;opossum&lt;br&gt;
config&lt;/code&gt; shows you up front which compose fields (if any) it will ignore, and&lt;br&gt;
&lt;code&gt;down&lt;/code&gt; cleans everything up.&lt;/p&gt;

&lt;p&gt;Based on this sample: &lt;strong&gt;48% of real-world stacks ran with zero changes, 62%&lt;br&gt;
with at most one line&lt;/strong&gt; — and the failures concentrated in stale samples and&lt;br&gt;
Docker-specific tooling, not in the runtime.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;https://github.com/suruseas/opossum&lt;/a&gt; — and I'd genuinely like to grow&lt;br&gt;
this survey: if you run &lt;code&gt;opossum up&lt;/code&gt; on a real compose file and it breaks,&lt;br&gt;
tell me what broke (here or in an issue). The failure catalog is the product.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>macos</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>Run your docker-compose.yml on Apple's container runtime</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Mon, 06 Jul 2026 16:49:45 +0000</pubDate>
      <link>https://dev.to/suruseas/run-your-docker-composeyml-on-apples-container-runtime-10o3</link>
      <guid>https://dev.to/suruseas/run-your-docker-composeyml-on-apples-container-runtime-10o3</guid>
      <description>&lt;p&gt;If you develop on a Mac and reach for &lt;code&gt;docker compose&lt;/code&gt; to spin up a multi-service&lt;br&gt;
stack, you might like &lt;strong&gt;&lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;opossum&lt;/a&gt;&lt;/strong&gt; — a small,&lt;br&gt;
Docker Compose–like orchestrator for &lt;strong&gt;&lt;a href="https://github.com/apple/container" rel="noopener noreferrer"&gt;Apple's &lt;code&gt;container&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
runtime on macOS 26.&lt;/p&gt;

&lt;p&gt;The pitch is simple: &lt;strong&gt;point it at a &lt;code&gt;docker-compose.yml&lt;/code&gt; you already have and run&lt;br&gt;
&lt;code&gt;opossum up&lt;/code&gt;.&lt;/strong&gt; The commands and mental model are the same ones you already know —&lt;br&gt;
&lt;code&gt;up&lt;/code&gt;, &lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;down&lt;/code&gt; — but everything runs on Apple's native &lt;code&gt;container&lt;/code&gt;&lt;br&gt;
runtime (a lightweight VM per container) instead of Docker Desktop.&lt;/p&gt;

&lt;p&gt;Let me show you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;opossum ships as a Homebrew formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;suruseas/opossum/opossum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two one-time steps (Apple &lt;code&gt;container&lt;/code&gt; prerequisites):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;container system start                    &lt;span class="c"&gt;# start the runtime&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;container system dns create opossum  &lt;span class="c"&gt;# register a local DNS domain so&lt;/span&gt;
                                          &lt;span class="c"&gt;# services can find each other by name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. (Requires macOS 26 on Apple silicon, since the runtime's&lt;br&gt;
container-to-container networking relies on macOS 26 features.)&lt;/p&gt;
&lt;h2&gt;
  
  
  Just try it on a project you already have
&lt;/h2&gt;

&lt;p&gt;The fastest way to get a feel for it: &lt;code&gt;cd&lt;/code&gt; into a directory you already run with&lt;br&gt;
&lt;code&gt;docker compose&lt;/code&gt; and run &lt;code&gt;opossum up&lt;/code&gt;. For a lot of stacks it just works — same&lt;br&gt;
file, same command, nothing to change. (opossum finds your &lt;code&gt;compose.yaml&lt;/code&gt; or&lt;br&gt;
&lt;code&gt;docker-compose.yml&lt;/code&gt; automatically.)&lt;/p&gt;

&lt;p&gt;And it's &lt;strong&gt;safe to try side by side with Docker.&lt;/strong&gt; opossum drives Apple's&lt;br&gt;
&lt;code&gt;container&lt;/code&gt; runtime, which is entirely separate from Docker — its own images,&lt;br&gt;
containers, and volumes — so running &lt;code&gt;opossum up&lt;/code&gt; won't touch your Docker&lt;br&gt;
containers or data.&lt;/p&gt;

&lt;p&gt;To follow along in this post, here's a tiny stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:80"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;opossum up&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opossum up
&lt;span class="go"&gt;Creating network intro-net
Starting redis (redis:7)
redis.intro.opossum
Starting web (nginx:alpine)
web.intro.opossum
  ↳ web on the host: localhost:8080
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It starts the services in dependency order (redis before web, because &lt;code&gt;web&lt;/code&gt;&lt;br&gt;
&lt;code&gt;depends_on&lt;/code&gt; it) and tells you where to reach a published service from the host —&lt;br&gt;
&lt;code&gt;localhost:8080&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;opossum ps&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Same output you'd expect from &lt;code&gt;docker compose ps&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opossum ps
&lt;span class="go"&gt;SERVICE  CONTAINER            IMAGE         IP            PORTS                 STATUS
redis    redis.intro.opossum  redis:7       192.168.67.2  -                     running
&lt;/span&gt;&lt;span class="gp"&gt;web      web.intro.opossum    nginx:alpine  192.168.67.3  0.0.0.0:8080-&amp;gt;&lt;/span&gt;80/tcp  running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8080&lt;/code&gt; in your browser (or curl it) and you get nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8080/ | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-4&lt;/span&gt;
&lt;span class="gp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Services find each other by name
&lt;/h2&gt;

&lt;p&gt;Just like Compose, peers reach each other by their &lt;strong&gt;service name&lt;/strong&gt; over the&lt;br&gt;
project's network. From inside the &lt;code&gt;web&lt;/code&gt; container, &lt;code&gt;redis&lt;/code&gt; resolves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opossum &lt;span class="nb"&gt;exec &lt;/span&gt;web sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"getent hosts redis"&lt;/span&gt;
&lt;span class="go"&gt;...  redis.intro.opossum  redis
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So your app connects to &lt;code&gt;redis:6379&lt;/code&gt; — no IPs, no hardcoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;opossum logs&lt;/code&gt; and &lt;code&gt;opossum stats&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Follow logs like usual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opossum logs redis
&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;1:M 06 Jul 2026 16:43:09.983 * Ready to accept connections tcp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there's a &lt;code&gt;docker stats&lt;/code&gt;–style live view of resource usage per service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opossum stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt;
&lt;span class="go"&gt;Container ID         Cpu %  Memory Usage          Net Rx/Tx             Block I/O             Pids
redis.intro.opossum  0.74%  29.91 MiB / 1.00 GiB  16.18 KiB / 0.57 KiB  25.68 MiB / 0.00 KiB  6
web.intro.opossum    0.00%  15.48 MiB / 1.00 GiB  15.15 KiB / 2.14 KiB  9.94 MiB / 4.00 KiB   6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;opossum down&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;opossum down
&lt;span class="go"&gt;Stopping web
Stopping redis
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tears everything down in reverse order and removes the project network. Add &lt;code&gt;-v&lt;/code&gt;&lt;br&gt;
to also drop named volumes.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's &lt;em&gt;almost&lt;/em&gt; the same as docker compose
&lt;/h2&gt;

&lt;p&gt;If you know Compose, you already know opossum. The everyday commands map 1:1:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You'd type in Compose&lt;/th&gt;
&lt;th&gt;With opossum&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose up&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opossum up&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose ps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opossum ps&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose logs -f web&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opossum logs web -f&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose exec web sh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opossum exec -it web sh&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose down -v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opossum down -v&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;opossum also has &lt;code&gt;stats&lt;/code&gt;, &lt;code&gt;images&lt;/code&gt;, &lt;code&gt;pull&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt;/&lt;code&gt;stop&lt;/code&gt;/&lt;br&gt;
&lt;code&gt;restart&lt;/code&gt;/&lt;code&gt;kill&lt;/code&gt;, and &lt;code&gt;config&lt;/code&gt; — the usual toolbox.&lt;/p&gt;

&lt;p&gt;A few honest differences, since it targets Apple's runtime rather than Docker:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One-time setup&lt;/strong&gt;: the &lt;code&gt;sudo container system dns create opossum&lt;/code&gt; step above,
which is what makes bare-name service discovery work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt;: it's Apple &lt;code&gt;container&lt;/code&gt; (a per-container lightweight VM), not Docker
Engine. Compose fields the runtime can't act on (like custom &lt;code&gt;networks:&lt;/code&gt; or
&lt;code&gt;container_name:&lt;/code&gt;) are parsed and &lt;strong&gt;warned about&lt;/strong&gt;, not silently dropped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS 26 + Apple silicon&lt;/strong&gt; only.&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;suruseas/opossum/opossum
&lt;span class="nb"&gt;cd &lt;/span&gt;your-project-with-a-compose-file
opossum up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;a href="https://github.com/suruseas/opossum" rel="noopener noreferrer"&gt;https://github.com/suruseas/opossum&lt;/a&gt;. If you have a &lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;br&gt;
lying around, give &lt;code&gt;opossum up&lt;/code&gt; a try and see how far it gets — for a lot of&lt;br&gt;
stacks, the answer is "all the way."&lt;/p&gt;

</description>
      <category>docker</category>
      <category>macos</category>
      <category>containers</category>
      <category>devtools</category>
    </item>
    <item>
      <title>🐢 and 🐇 in My Claude Code Status Line: Now Watching the Week</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Mon, 11 May 2026 15:45:27 +0000</pubDate>
      <link>https://dev.to/suruseas/and-in-my-claude-code-status-line-now-watching-the-week-1m1e</link>
      <guid>https://dev.to/suruseas/and-in-my-claude-code-status-line-now-watching-the-week-1m1e</guid>
      <description>&lt;p&gt;I recently built a tortoise-and-hare status line for Claude Code that shows whether you're burning tokens faster than a steady pace. &lt;a href="https://dev.to/suruseas/and-in-my-claude-code-status-line-372f"&gt;That post is here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then &lt;a href="https://www.anthropic.com/news/higher-limits-spacex" rel="noopener noreferrer"&gt;Anthropic raised the 5h limit but tightened the weekly cap&lt;/a&gt;. Suddenly the 7-day limit became the one that actually bites — with the relaxed 5h window, hitting 100% in five hours takes real effort. The weekly cap doesn't.&lt;/p&gt;

&lt;p&gt;So I updated the script: 7d gets the full-width tortoise/hare bar, 5h keeps the same logic in a shorter bar on the right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Sonnet 4.6] ········🐢···········🐇 7d:105% ⚠️@5/14 | ··🐢··🐇··· 5h:70% ⚠️@18:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The &lt;code&gt;7d:105%&lt;/code&gt; is real — that's what I'm seeing right now. Going over 100% means you're into Claude Code's additional usage allowance beyond the base weekly limit. The 🐇 flies off the right edge of the bar when that happens.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.model.display_name'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;FIVE_H_PCT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'(.rate_limits.five_hour.used_percentage // 0)'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;SEVEN_D_PCT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'(.rate_limits.seven_day.used_percentage // 0)'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;FIVE_H_RESETS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.rate_limits.five_hour.resets_at // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;SEVEN_D_RESETS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.rate_limits.seven_day.resets_at // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;NOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Asia/Tokyo  &lt;span class="c"&gt;# change to your local timezone&lt;/span&gt;
&lt;span class="nv"&gt;W7&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;W5&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10   &lt;span class="c"&gt;# bar widths: 7d full, 5h half&lt;/span&gt;

make_bar&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="nv"&gt;ideal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt; &lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt; i
    &lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;0 &lt;span class="k"&gt;$((&lt;/span&gt;width &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ideal&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$actual&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;🐢🐇"&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ideal&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;🐢"&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$actual&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;🐇"&lt;/span&gt;
        &lt;span class="k"&gt;else &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;·"&lt;/span&gt;
        &lt;span class="k"&gt;fi
    done&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$actual&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ge&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;🐇"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$bar&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# 7-day window (main bar, width=20)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SEVEN_D_RESETS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;REMAINING_7D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;SEVEN_D_RESETS &lt;span class="o"&gt;-&lt;/span&gt; NOW&lt;span class="k"&gt;))&lt;/span&gt;
    &lt;span class="nv"&gt;RESET_7D_MD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SEVEN_D_RESETS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"+%m/%d"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;/ &lt;span class="s1"&gt;'{printf "%d/%d", $1, $2}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;RESET_7D_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RESET_7D_MD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMAINING_7D&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMAINING_7D&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 604800 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;IDEAL_7D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;604800&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; REMAINING_7D&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; W7 &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;604800&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nv"&gt;IDEAL_7D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
    &lt;span class="k"&gt;fi
else
    &lt;/span&gt;&lt;span class="nv"&gt;IDEAL_7D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;RESET_7D_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@?"&lt;/span&gt;
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nv"&gt;ACTUAL_7D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s2"&gt;"BEGIN {print int(&lt;/span&gt;&lt;span class="nv"&gt;$SEVEN_D_PCT&lt;/span&gt;&lt;span class="s2"&gt; * &lt;/span&gt;&lt;span class="nv"&gt;$W7&lt;/span&gt;&lt;span class="s2"&gt; / 100)}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;BAR_7D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;make_bar &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL_7D&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDEAL_7D&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$W7&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL_7D&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDEAL_7D&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;WARN_7D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;" ⚠️"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;WARN_7D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;SEVEN_D_DISP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%.0f"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SEVEN_D_PCT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 5-hour window (half bar, width=10)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FIVE_H_RESETS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;REMAINING_5H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;FIVE_H_RESETS &lt;span class="o"&gt;-&lt;/span&gt; NOW&lt;span class="k"&gt;))&lt;/span&gt;
    &lt;span class="nv"&gt;RESET_5H_JST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FIVE_H_RESETS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"+%H:%M"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;RESET_5H_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RESET_5H_JST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMAINING_5H&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMAINING_5H&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 18000 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;IDEAL_5H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;18000&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; REMAINING_5H&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; W5 &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;18000&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nv"&gt;IDEAL_5H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
    &lt;span class="k"&gt;fi
else
    &lt;/span&gt;&lt;span class="nv"&gt;IDEAL_5H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;RESET_5H_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@?"&lt;/span&gt;
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nv"&gt;ACTUAL_5H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s2"&gt;"BEGIN {print int(&lt;/span&gt;&lt;span class="nv"&gt;$FIVE_H_PCT&lt;/span&gt;&lt;span class="s2"&gt; * &lt;/span&gt;&lt;span class="nv"&gt;$W5&lt;/span&gt;&lt;span class="s2"&gt; / 100)}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;BAR_5H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;make_bar &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL_5H&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDEAL_5H&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$W5&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL_5H&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDEAL_5H&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;WARN_5H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;" ⚠️"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;WARN_5H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;FIVE_H_DISP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%.0f"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FIVE_H_PCT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MODEL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BAR_7D&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 7d:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SEVEN_D_DISP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WARN_7D&lt;/span&gt;&lt;span class="k"&gt;}${&lt;/span&gt;&lt;span class="nv"&gt;RESET_7D_TAG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BAR_5H&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 5h:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FIVE_H_DISP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WARN_5H&lt;/span&gt;&lt;span class="k"&gt;}${&lt;/span&gt;&lt;span class="nv"&gt;RESET_5H_TAG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;W7=20&lt;/code&gt; and &lt;code&gt;W5=10&lt;/code&gt; control bar widths. &lt;code&gt;18000&lt;/code&gt; = 5 hours in seconds, &lt;code&gt;604800&lt;/code&gt; = 7 days. Change &lt;code&gt;TZ=Asia/Tokyo&lt;/code&gt; at the top to your local timezone.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>programming</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>🐢 and 🐇 in My Claude Code Status Line</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Thu, 30 Apr 2026 09:28:08 +0000</pubDate>
      <link>https://dev.to/suruseas/and-in-my-claude-code-status-line-372f</link>
      <guid>https://dev.to/suruseas/and-in-my-claude-code-status-line-372f</guid>
      <description>&lt;p&gt;The Claude Code status line gives you a JSON blob and lets you render whatever you want. &lt;a href="https://code.claude.com/docs/en/statusline" rel="noopener noreferrer"&gt;Docs here.&lt;/a&gt; I made mine an Aesop's fable — borrowing the idea from &lt;a href="https://rabbit-shocker.org/ja/" rel="noopener noreferrer"&gt;Rabbit&lt;/a&gt;, a presentation tool by Kouhei Sutou that famously races a rabbit against a tortoise to show whether you're on pace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Sonnet 4.6] ········🐢····🐇······· 5h:62% ⚠️ | reset 18:00 | 7d:23%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;🐢 = where you &lt;em&gt;should&lt;/em&gt; be (time elapsed in the 5-hour window). &lt;/li&gt;
&lt;li&gt;🐇 = where you &lt;em&gt;actually&lt;/em&gt; are (tokens used).&lt;/li&gt;
&lt;li&gt;⚠️  = hare is ahead, you're burning too fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point: a raw "62%" means opposite things at hour 1 vs hour 4. Comparing actual pace to ideal pace is what I actually want to know — and the tortoise/hare visual does it at a glance, no mental math.&lt;/p&gt;

&lt;h2&gt;
  
  
  The script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.model.display_name'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;FIVE_H_PCT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'(.rate_limits.five_hour.used_percentage // 0)'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;SEVEN_D_PCT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'(.rate_limits.seven_day.used_percentage // 0) * 10 | floor | . / 10'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;RESETS_AT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.rate_limits.five_hour.resets_at // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESETS_AT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;REMAINING_SEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;RESETS_AT &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
    &lt;span class="nv"&gt;RESET_JST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Asia/Tokyo &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESETS_AT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"+%H:%M"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;RESET_STR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;" | reset &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RESET_JST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMAINING_SEC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REMAINING_SEC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 18000 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;IDEAL_PCT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;18000&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; REMAINING_SEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;18000&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nv"&gt;IDEAL_PCT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
    &lt;span class="k"&gt;fi
else
    &lt;/span&gt;&lt;span class="nv"&gt;IDEAL_PCT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;RESET_STR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;BAR_WIDTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20
&lt;span class="nv"&gt;ACTUAL_POS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s2"&gt;"BEGIN {print int(&lt;/span&gt;&lt;span class="nv"&gt;$FIVE_H_PCT&lt;/span&gt;&lt;span class="s2"&gt; * &lt;/span&gt;&lt;span class="nv"&gt;$BAR_WIDTH&lt;/span&gt;&lt;span class="s2"&gt; / 100)}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;IDEAL_POS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;IDEAL_PCT &lt;span class="o"&gt;*&lt;/span&gt; BAR_WIDTH &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;0 &lt;span class="k"&gt;$((&lt;/span&gt;BAR_WIDTH &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDEAL_POS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL_POS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;🐢🐇"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDEAL_POS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;🐢"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL_POS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;🐇"&lt;/span&gt;
    &lt;span class="k"&gt;else &lt;/span&gt;&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;·"&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL_POS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDEAL_POS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;WARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;" ⚠️"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;WARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;FIVE_H_DISP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FIVE_H_PCT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{printf "%.0f", $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="nv"&gt;$MODEL&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 5h:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FIVE_H_DISP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WARN&lt;/span&gt;&lt;span class="k"&gt;}${&lt;/span&gt;&lt;span class="nv"&gt;RESET_STR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | 7d:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SEVEN_D_PCT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;18000&lt;/code&gt; is 5 hours in seconds. Drop &lt;code&gt;TZ=Asia/Tokyo&lt;/code&gt; for your local zone.&lt;/p&gt;

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

&lt;p&gt;Save as &lt;code&gt;~/.claude/statusline.sh&lt;/code&gt;, &lt;code&gt;chmod +x&lt;/code&gt; it, then in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/.claude/statusline.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Claude Code. Done.&lt;/p&gt;




&lt;p&gt;The rabbit-vs-tortoise pacing metaphor is lifted straight from &lt;a href="https://rabbit-shocker.org/ja/" rel="noopener noreferrer"&gt;Rabbit&lt;/a&gt; (うさぎとかめ). Same idea, different domain — slide timer → token budget.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>programming</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Stop Counting Prompts — Start Reflecting on AI Fluency</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Sat, 28 Mar 2026 05:03:07 +0000</pubDate>
      <link>https://dev.to/suruseas/stop-counting-prompts-start-reflecting-on-ai-fluency-32n4</link>
      <guid>https://dev.to/suruseas/stop-counting-prompts-start-reflecting-on-ai-fluency-32n4</guid>
      <description>&lt;h2&gt;
  
  
  "I'm the best at piloting this thing!"
&lt;/h2&gt;

&lt;p&gt;There's a famous line from a Japanese mecha anime — the protagonist screams: &lt;strong&gt;「僕が一番ガンダムをうまく使えるんだ！」&lt;/strong&gt; — &lt;em&gt;"I'm the one who can pilot this Gundam the best!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you use AI coding tools every day, you've probably felt something similar. That sense of clicking with the AI. Knowing you're getting more out of it than most people around you.&lt;/p&gt;

&lt;p&gt;But how do you show that?&lt;/p&gt;

&lt;p&gt;"Look at my PR count"? "Check how many lines I generated"? That's not it. Those numbers don't capture the &lt;em&gt;feel&lt;/em&gt; of working well with AI. That nagging gap between what you &lt;em&gt;know&lt;/em&gt; and what you can &lt;em&gt;prove&lt;/em&gt; is what got me started.&lt;/p&gt;

&lt;h2&gt;
  
  
  My own question, answered
&lt;/h2&gt;

&lt;p&gt;In my previous post, I asked:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/suruseas/i-deleted-66-of-my-ai-coding-guide-heres-what-survived-55i6" class="crayons-story__hidden-navigation-link"&gt;I Deleted 66% of My AI Coding Guide — Here's What Survived&lt;/a&gt;


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

          &lt;a href="/suruseas" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F852203%2Fe8e2c8e4-a9f6-4726-9878-22f90505518c.jpeg" alt="suruseas profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/suruseas" class="crayons-story__secondary fw-medium m:hidden"&gt;
              yukihiro amadatsu
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                yukihiro amadatsu
                
              
              &lt;div id="story-author-preview-content-3367393" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/suruseas" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F852203%2Fe8e2c8e4-a9f6-4726-9878-22f90505518c.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;yukihiro amadatsu&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/suruseas/i-deleted-66-of-my-ai-coding-guide-heres-what-survived-55i6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 18&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/suruseas/i-deleted-66-of-my-ai-coding-guide-heres-what-survived-55i6" id="article-link-3367393"&gt;
          I Deleted 66% of My AI Coding Guide — Here's What Survived
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/codequality"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;codequality&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/suruseas/i-deleted-66-of-my-ai-coding-guide-heres-what-survived-55i6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/suruseas/i-deleted-66-of-my-ai-coding-guide-heres-what-survived-55i6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

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

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

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

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Is your team measuring AI coding productivity by any of these?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Common metric&lt;/th&gt;
&lt;th&gt;What it actually rewards&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lines of code generated&lt;/td&gt;
&lt;td&gt;Volume targets promote bloat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number of prompts per session&lt;/td&gt;
&lt;td&gt;High count may signal poor instructions, not hard work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response speed&lt;/td&gt;
&lt;td&gt;Penalizes people who think before they ask&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commit count&lt;/td&gt;
&lt;td&gt;Easily inflated by splitting work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number of AI tools adopted&lt;/td&gt;
&lt;td&gt;Using ≠ using well&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They all measure &lt;em&gt;quantity&lt;/em&gt;. But whether you're actually good at working with AI never shows up in volume metrics.&lt;/p&gt;

&lt;p&gt;That article laid out three enduring principles: &lt;strong&gt;keep things reversible, make intent explicit, verify outputs&lt;/strong&gt;. AI Fluency is my attempt to turn those into a structured self-reflection — not a score that ranks you, but a mirror that shows you &lt;em&gt;how&lt;/em&gt; you collaborate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Fluency"?
&lt;/h2&gt;

&lt;p&gt;Fluency — like language fluency. When you're fluent, conversation flows naturally. You don't stumble, backtrack, or struggle to express what you mean.&lt;/p&gt;

&lt;p&gt;Working with AI has a similar feel. When it's going well, your instructions and the AI's output click, and the work just &lt;em&gt;flows&lt;/em&gt;. When it's not, you're stuck in loops of corrections and rework.&lt;/p&gt;

&lt;p&gt;AI Fluency tries to visualize that — &lt;em&gt;how naturally you collaborate with AI&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here's what I built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsar5mxi2rwmlzujgco2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsar5mxi2rwmlzujgco2o.png" alt="AI Fluency scorecard showing a 5-axis radar chart with style type, rank, and axis scores" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My result: &lt;strong&gt;The Explorer&lt;/strong&gt; — the type that explores new ways of using AI. Strong in Breadth, with room to grow in Precision.&lt;/p&gt;

&lt;p&gt;Full ability sheet: &lt;a href="https://github.com/suruseas/ai-fluency/blob/main/output/profile.md" rel="noopener noreferrer"&gt;profile.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/suruseas/ai-fluency" rel="noopener noreferrer"&gt;github.com/suruseas/ai-fluency&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Also available in Japanese on &lt;a href="https://qiita.com/suruseas/items/1f4f701f439fded0cb40" rel="noopener noreferrer"&gt;Qiita&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A Different Yardstick: 5 Axes
&lt;/h2&gt;

&lt;p&gt;By breaking down what "fluency" actually means in practice, I landed on five dimensions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context Design&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Setting up the environment so the AI can do its best work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Precision&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Communicating intent clearly with minimal back-and-forth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Steering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Guiding AI output in the right direction; judging quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Actually delivering value through AI collaboration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Breadth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Using AI's capabilities across diverse tasks, not just one pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first three map to the enduring principles from the previous article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Make intent explicit" → &lt;strong&gt;Context Design&lt;/strong&gt; + &lt;strong&gt;Precision&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;"Verify outputs" → &lt;strong&gt;Steering&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The remaining two go further — asking whether the collaboration actually produces results (&lt;strong&gt;Output&lt;/strong&gt;) and whether you're using AI's full range or stuck in a single pattern (&lt;strong&gt;Breadth&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Not "fewer prompts is better" — but "can you get it right with fewer exchanges?" Not "more commits" — but "are you actually achieving your goals?" That shift — from quantity to quality — is the whole point.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on methodology
&lt;/h3&gt;

&lt;p&gt;These five axes weren't derived from a literature review or formal research. They emerged from iterating with AI itself — breaking down what "good collaboration" felt like across dozens of my own sessions, then pressure-testing the categories until they stopped overlapping. It's an opinionated framework, not a scientific instrument. I think that's okay for a self-reflection tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  16 Style Types — A Bit of Fun That Stuck
&lt;/h2&gt;

&lt;p&gt;After generating the 5-axis scores, I realized raw numbers are hard to talk about. "My Context Design is 76 and Breadth is 96" — not exactly cocktail-party material.&lt;/p&gt;

&lt;p&gt;So I put together a personality-type system on a whim — classifying people by the &lt;em&gt;shape&lt;/em&gt; of their radar chart. It turned out to be surprisingly intuitive, so it stuck.&lt;/p&gt;

&lt;p&gt;The types are determined by &lt;strong&gt;which axes stand out&lt;/strong&gt;, not by how high your scores are. It's about style, not rank. Here are a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Sniper&lt;/strong&gt; (Precision) — Minimum input, maximum output. One-shot instructions that just work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Architect&lt;/strong&gt; (Context Design) — Master of setting the stage. The AI barely needs to ask questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Explorer&lt;/strong&gt; (Breadth) — Always finding new ways to use AI. First to try MCP, plugins, sub-agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Surgeon&lt;/strong&gt; (Precision + Steering) — Precision and finesse for tough problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Virtuoso&lt;/strong&gt; (Balanced) — Well-rounded across all axes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are 16 types in total (1 balanced + 5 primary + 10 hybrid). &lt;a href="https://github.com/suruseas/ai-fluency" rel="noopener noreferrer"&gt;See the full list in the repo.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;High scores across the board aren't the goal — every shape has meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest Limitations
&lt;/h2&gt;

&lt;p&gt;This is a &lt;strong&gt;self-reflection tool&lt;/strong&gt;, not a performance metric. A couple of things to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scores vary between runs.&lt;/strong&gt; The qualitative assessment uses an LLM, so results aren't deterministic. That's a tradeoff of using AI-based evaluation — I leaned into it by emphasizing &lt;em&gt;shape&lt;/em&gt; over absolute numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Style is personal, not comparable.&lt;/strong&gt; Common rubric, but not an identical scale. Your "72" and someone else's "72" don't mean the same thing. The radar chart shape is what matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also an inherent circularity worth naming: the tool uses an LLM to evaluate how well you work with an LLM. It may have blind spots — for example, favoring verbose sessions over terse-but-expert ones. I don't have a fix for that yet, but I think the transparency of the framework (all scoring logic is in the repo) helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use It
&lt;/h2&gt;

&lt;p&gt;It currently supports &lt;strong&gt;Claude Code&lt;/strong&gt; session data. The five axes themselves are agent-agnostic by design — support for other agents is planned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone &amp;amp; setup&lt;/span&gt;
git clone https://github.com/suruseas/ai-fluency.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ai-fluency
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# 2. Generate session analysis in Claude Code&lt;/span&gt;
claude&amp;gt; /insights

&lt;span class="c"&gt;# 3. Generate your scorecard (English output)&lt;/span&gt;
npm run score           &lt;span class="c"&gt;# → output/scores.json&lt;/span&gt;
npm run card            &lt;span class="c"&gt;# → output/card-dark.svg, card-light.svg&lt;/span&gt;
npm run profile         &lt;span class="c"&gt;# → output/profile.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Or even simpler:&lt;/strong&gt; if you're already in a Claude Code session, just type &lt;strong&gt;&lt;code&gt;/ai-fluency&lt;/code&gt;&lt;/strong&gt; — it handles everything in one shot.&lt;/p&gt;

&lt;p&gt;This produces SVG cards (dark/light themes) and a Markdown ability sheet in &lt;code&gt;output/&lt;/code&gt;. To embed the card in your GitHub README, &lt;a href="https://github.com/suruseas/ai-fluency#embedding-in-your-readme" rel="noopener noreferrer"&gt;see the instructions in the repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This started as my answer to a question I posed in my previous article: if lines of code and prompt counts are the wrong metrics, what &lt;em&gt;should&lt;/em&gt; we look at?&lt;/p&gt;

&lt;p&gt;My answer: &lt;strong&gt;Context Design, Precision, Steering, Output, Breadth&lt;/strong&gt; — the quality of human-AI collaboration, not the volume.&lt;/p&gt;

&lt;p&gt;It's not a perfect tool. But if it makes you stop and think, "Huh, so &lt;em&gt;that's&lt;/em&gt; how I work with AI" — that's enough.&lt;/p&gt;

&lt;p&gt;If you use Claude Code, it takes about 2 minutes. Drop your type in the comments — I'll compile the dev.to distribution in a follow-up post!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/suruseas" rel="noopener noreferrer"&gt;
        suruseas
      &lt;/a&gt; / &lt;a href="https://github.com/suruseas/ai-fluency" rel="noopener noreferrer"&gt;
        ai-fluency
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI Fluency - Score your AI collaboration style across 5 axes
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;AI Fluency&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;GitHub プロフィールに貼れる「AI活用力」のスコアカード生成ツール。&lt;/p&gt;

&lt;p&gt;AI エージェントとの協働スタイルを5軸でスコアリングし、SVGカードと能力シートを出力します。評価軸はエージェント共通の設計ですが、現時点では &lt;strong&gt;Claude Code&lt;/strong&gt; のセッション分析データ（&lt;code&gt;/insights&lt;/code&gt; で生成）に対応しています。&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;カード例&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fsuruseas%2Fai-fluency%2F.%2Foutput%2Fcard-light.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fsuruseas%2Fai-fluency%2F.%2Foutput%2Fcard-light.svg" alt="AI Fluency Card"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;前提条件&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;現在の対応エージェント: &lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.anthropic.com/en/docs/claude-code" rel="nofollow noopener noreferrer"&gt;Claude Code&lt;/a&gt; がインストール済みであること&lt;/li&gt;
&lt;li&gt;Claude Code でのセッション履歴があること（分析対象データとして必要）&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;他の AI エージェントへの対応は今後追加予定です。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;試してみる&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. リポジトリをクローンしてセットアップ&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/suruseas/ai-fluency.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; ai-fluency
npm install&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;2. facets データを生成する&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Claude Code で &lt;code&gt;/insights&lt;/code&gt; を実行してください。セッション履歴が分析され、&lt;code&gt;~/.claude/usage-data/facets/&lt;/code&gt; に facets データ（JSON）が生成されます。&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;claude&amp;gt; /insights
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; facets データがない状態では以降のステップは実行できません。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;3. スコアカードを生成する&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;クローンしたディレクトリで Claude Code を起動し、&lt;code&gt;/ai-fluency&lt;/code&gt; を実行します。&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;cd&lt;/span&gt; ai-fluency
claude&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;claude&amp;gt; /ai-fluency
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;直近3ヶ月分のデータを対象に、スコア算出からカード・能力シートの生成まですべて自動で行われます。&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;手動で実行する場合&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 定量スコアのみ算出（output/scores.json に出力）&lt;/span&gt;
npm run score

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; SVG カードを生成（output/card-dark.svg, card-light.svg）&lt;/span&gt;
npm run card:ja     &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 日本語版&lt;/span&gt;
npm run card        &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 英語版（デフォルト）&lt;/span&gt;

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 能力シートを生成（output/profile.md）&lt;/span&gt;
npm run profile:ja  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 日本語版&lt;/span&gt;
npm run profile     &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 英語版（デフォルト）&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;生成物はすべて &lt;code&gt;output/&lt;/code&gt; ディレクトリに出力されます。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意:&lt;/strong&gt; 生成物にはプロジェクト固有の情報は仕組み上含まれませんが、&lt;code&gt;output/profile.md&lt;/code&gt; に機密情報が出力されていないことを公開前にご確認ください。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;3. README に埋め込む&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-text-md notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&amp;lt;&lt;span class="pl-ent"&gt;picture&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span class="pl-ent"&gt;source&lt;/span&gt; &lt;span class="pl-e"&gt;media&lt;/span&gt;=&lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="pl-s"&gt;"&lt;/span&gt; &lt;span class="pl-e"&gt;srcset&lt;/span&gt;=&lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;https://raw.githubusercontent.com/suruseas/ai-fluency/main/output/card-dark.svg&lt;/span&gt;&lt;span class="pl-s"&gt;"&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span class="pl-ent"&gt;img&lt;/span&gt; &lt;span class="pl-e"&gt;src&lt;/span&gt;=&lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;https://raw.githubusercontent.com/suruseas/ai-fluency/main/output/card-light.svg&lt;/span&gt;&lt;span class="pl-s"&gt;"&lt;/span&gt; &lt;span class="pl-e"&gt;alt&lt;/span&gt;=&lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;AI Fluency&lt;/span&gt;&lt;span class="pl-s"&gt;"&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span class="pl-ent"&gt;picture&lt;/span&gt;&amp;gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;スコアについて&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;スコアの一部（定性評価）は AI がセッション内容を読み取って判定しています。そのため以下の点にご注意ください。&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;再現性はありません&lt;/strong&gt; — 同じデータでも実行ごとにスコアが多少変動します&lt;/li&gt;

&lt;li&gt;…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/suruseas/ai-fluency" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>devtools</category>
    </item>
    <item>
      <title>I Deleted 66% of My AI Coding Guide — Here's What Survived</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Wed, 18 Mar 2026 11:14:57 +0000</pubDate>
      <link>https://dev.to/suruseas/i-deleted-66-of-my-ai-coding-guide-heres-what-survived-55i6</link>
      <guid>https://dev.to/suruseas/i-deleted-66-of-my-ai-coding-guide-heres-what-survived-55i6</guid>
      <description>&lt;p&gt;I started with 252 lines of AI coding principles. After four rounds of review, 86 lines survived. But first — a question.&lt;/p&gt;

&lt;p&gt;Is your team measuring AI coding productivity by any of these?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lines of code generated&lt;/li&gt;
&lt;li&gt;Number of prompts per session&lt;/li&gt;
&lt;li&gt;Response speed&lt;/li&gt;
&lt;li&gt;Commit count&lt;/li&gt;
&lt;li&gt;Number of AI tools adopted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If so, you might be optimizing for the wrong things. Lines-of-code targets reward bloat. Prompt-count targets punish thinking. Speed targets skip verification. When these metrics become goals, quality pays the price.&lt;/p&gt;

&lt;p&gt;I wrote an 86-line document called &lt;strong&gt;The AI Coding Way&lt;/strong&gt; that explains why — and what to measure instead. It tries to capture what stays true about human-AI coding collaboration, regardless of which tool or model you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Three principles&lt;/strong&gt;, in order of priority:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep things reversible&lt;/strong&gt; (prerequisite) — Linters, tests, CI, version control. Safety enables boldness. You can't ask AI to refactor a module if you can't undo it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make your intent explicit&lt;/strong&gt; (starting point) — Every context you give AI has three elements: purpose, constraints, and knowledge. Missing any one of them degrades output quality. Project-level intent (types, tests, naming conventions) compounds across every session.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Verify the output&lt;/strong&gt; (non-negotiable) — The bottleneck has shifted from generation to verification. Code that was generated fast should be reviewed slow. The most expensive decision in AI coding: "it works, ship it."&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;One practice section&lt;/strong&gt; covering the collaboration cycle (instruct → generate → verify → improve) and two habits: ask questions instead of giving instructions, and turn repeated instructions into project-level conventions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One measurement section&lt;/strong&gt; listing six metrics that are tempting to track but dangerous if used as goals: lines generated, prompt count, response speed, commit count, session count, tool count. These aren't useless — but optimizing for them alone leads you away from what matters. Measure density instead: acceptance rate, rework frequency, final code quality.&lt;/p&gt;

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

&lt;p&gt;No tool names. No model names. No programming languages. No prompt templates. No opinions on whether AI is your boss, your colleague, your subordinate, or your tool — that's your call. The three principles apply regardless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I wrote it
&lt;/h2&gt;

&lt;p&gt;There are plenty of AI coding guides out there. Anthropic, OpenAI, and countless blog posts tell you how to write better prompts. But most of them have a shelf life of about six months — the next model update makes half the advice obsolete.&lt;/p&gt;

&lt;p&gt;I wanted something different: a set of principles that hold true even as models get smarter, context windows get larger, and tools come and go.&lt;/p&gt;

&lt;p&gt;So I set a rule: &lt;strong&gt;don't write anything that a better model would invalidate.&lt;/strong&gt; "AI hallucinates" is a current fact, not a lasting principle. "AI output is probabilistic" is a lasting principle. "Context windows are small" will age poorly. "Humans are responsible for verifying output" won't.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I wrote it
&lt;/h2&gt;

&lt;p&gt;This document is itself a product of AI coding. AI agents debated the structure. AI generated the text. I made every decision on what to keep, what to cut, and how to frame it — exactly the cycle the document describes.&lt;/p&gt;

&lt;p&gt;It started at 252 lines. Four AI agents reviewed it. One of them said: &lt;em&gt;"A blog post would have been enough."&lt;/em&gt; That forced me to ask what actually survives if you strip everything away. For example, a full section called "AI's Amplification Effect" was cut as a standalone section — but its core insight ("AI amplifies both good and bad design") survived as two lines in the principles preamble. That's the kind of compression that happened across the board. The answer was 86 lines.&lt;/p&gt;

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

&lt;p&gt;Here it is — the entire thing. 86 lines.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AI Coding Way
&lt;/h2&gt;

&lt;p&gt;Principles for AI Coding — v0.1, March 2026&lt;/p&gt;

&lt;p&gt;If you write code with AI and want to get better at it, these principles are for you. Being a good engineer is the best AI strategy.&lt;/p&gt;

&lt;p&gt;This document is meant to live in your project repository — not read once and forgotten, but referenced daily as shared understanding across your team. It will be revised based on real-world feedback.&lt;/p&gt;




&lt;h3&gt;
  
  
  Three Principles
&lt;/h3&gt;

&lt;p&gt;AI output is probabilistic. The same instruction can produce different code. AI has knowledge gaps and states incorrect things with confidence. And AI amplifies — good intent produces good code at scale; sloppy instructions produce plausible but fragile code at scale. Given these properties, humans bear three responsibilities: intent, context, and verification.&lt;/p&gt;

&lt;p&gt;These three principles are non-negotiable requirements for AI coding. The numbers indicate the order you should address them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Keep things reversible (prerequisite)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The foundation for everything else. Without this, nothing is safe to try.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prevention:&lt;/em&gt; Type checking, linters, test suites, CI. If AI-generated code doesn't meet the bar, it gets rejected automatically. The stronger your prevention, the bolder you can delegate.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Recovery:&lt;/em&gt; Version control, branching strategies, snapshots. The last line of defense when prevention fails.&lt;/p&gt;

&lt;p&gt;Safety mechanisms are not constraints. They are the foundation that enables bold delegation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Make your intent explicit (starting point)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Generating and verifying without clear intent is running without a map.&lt;/p&gt;

&lt;p&gt;Context has three elements: &lt;strong&gt;purpose&lt;/strong&gt; (what you want to achieve), &lt;strong&gt;constraints&lt;/strong&gt; (what must not happen), and &lt;strong&gt;knowledge&lt;/strong&gt; (background information needed for decisions). Without purpose, AI wanders. Without constraints, you get unwanted output. Without knowledge, AI guesses.&lt;/p&gt;

&lt;p&gt;Intent operates at two levels. At the &lt;em&gt;task level&lt;/em&gt;, you communicate purpose, constraints, and knowledge in your instructions. At the &lt;em&gt;project level&lt;/em&gt;, type definitions, tests, naming conventions, and directory structure express intent — set these up once and they improve every session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Verify the output (non-negotiable)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The bottleneck has shifted from generation to verification. Generation takes seconds. Verification takes human time. That's why the quality of your verification determines the quality of your outcomes.&lt;/p&gt;

&lt;p&gt;The faster code was generated, the more carefully you should read it.&lt;/p&gt;

&lt;p&gt;A common failure: AI generates 200 lines that appear to work. Tests pass. But half is unnecessary abstraction from trying to be too clean, and the rest silently swallows errors and breaks on edge cases. "It works, merge it" is the most expensive decision in AI coding.&lt;/p&gt;




&lt;h3&gt;
  
  
  Practice
&lt;/h3&gt;

&lt;p&gt;AI coding follows this cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Communicate&lt;/strong&gt; — Pass purpose, constraints, and necessary knowledge to AI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate&lt;/strong&gt; — AI produces code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify&lt;/strong&gt; — Human judges the output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve&lt;/strong&gt; — Revise instructions and context based on results&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skipping step 4 and jumping back to step 1 is the primary cause of spinning your wheels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask questions, not just instructions.&lt;/strong&gt; "Is there a problem with this design?" draws out more of AI's capability than "Write this function." When AI offers suggestions, dismissing them as "not what I asked for" is a missed opportunity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're writing the same instructions every time, turn them into conventions.&lt;/strong&gt; Put them in a rules file. Automate with hooks. What you systematize compounds across every future session.&lt;/p&gt;




&lt;h3&gt;
  
  
  What to Measure
&lt;/h3&gt;

&lt;p&gt;The only measure of progress is working software.&lt;/p&gt;

&lt;p&gt;The following metrics are tempting to track but dangerous when used as goals. They aren't useless as signals — but optimizing for them leads you away from what matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lines generated.&lt;/strong&gt; Measuring quantity incentivizes quantity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt count.&lt;/strong&gt; Many exchanges signal low instruction quality, not productivity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response speed.&lt;/strong&gt; Penalizes people who think before they instruct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit count.&lt;/strong&gt; Split commits and the number goes up. Measuring quantity invites inflation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session count.&lt;/strong&gt; More isn't better. Context loss from session breaks can reduce efficiency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number of tools used.&lt;/strong&gt; Using a tool and mastering a tool are different things.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common problem: measuring quantity and speed incentivizes quantity and speed at the expense of quality.&lt;/p&gt;

&lt;p&gt;Measure density instead. Acceptance rate. Rework frequency. Final code quality.&lt;/p&gt;




&lt;p&gt;This document is a product of AI coding. AI agents debated, AI generated text, and a human made the decisions, verified the output, and revised it.&lt;/p&gt;

&lt;p&gt;Whether you see AI as a subordinate, a collaborator, a supervisor, or a tool is up to you. The three principles apply regardless of what you expect from AI.&lt;/p&gt;




&lt;h2&gt;
  
  
  v0.1
&lt;/h2&gt;

&lt;p&gt;This is v0.1, not a finished product. If you have feedback, I want to hear it. The only way this earns the right to be called a "guide" someday is through revision.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>codequality</category>
    </item>
    <item>
      <title>When AI Joins Your Team, Where Should You Focus Your Resources?</title>
      <dc:creator>yukihiro amadatsu</dc:creator>
      <pubDate>Thu, 12 Mar 2026 12:42:44 +0000</pubDate>
      <link>https://dev.to/suruseas/when-ai-joins-your-team-where-should-you-focus-your-resources-3p23</link>
      <guid>https://dev.to/suruseas/when-ai-joins-your-team-where-should-you-focus-your-resources-3p23</guid>
      <description>&lt;p&gt;I stopped thinking of AI as a tool.&lt;/p&gt;

&lt;p&gt;Copilot, Cursor, Claude Code — once I started treating these not as "convenient assistants" but as "talented teammates," the way I develop software changed entirely.&lt;/p&gt;

&lt;p&gt;And from that perspective, a simple question emerges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If an exceptional coder and an exceptional reviewer were already on your team, where should the team focus its resources?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Don't Treat AI as Special — Just as an Exceptionally Talented Human
&lt;/h2&gt;

&lt;p&gt;First, an important premise.&lt;/p&gt;

&lt;p&gt;AI is not special. Treat it the same as a highly skilled human engineer.&lt;/p&gt;

&lt;p&gt;AI can already write code, open pull requests, and leave review comments. The range of what it can do is expanding rapidly, and the justification for treating it differently "because it's AI" is fading. Think of it simply as having an exceptionally talented human engineer join the team.&lt;/p&gt;

&lt;p&gt;With that premise in place, the opening question takes on meaning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Answer Is: Focus on Review
&lt;/h2&gt;

&lt;p&gt;The conclusion first.&lt;/p&gt;

&lt;p&gt;The team should focus on the review side — the process of questioning answers.&lt;/p&gt;

&lt;p&gt;The reason is simple.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generation (opening PRs) — let AI go all out.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Writing code is the process of "producing an answer given a set of requirements." This is where AI excels, and there's little reason for humans to run alongside it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review (questioning answers) — requires a perspective outside the generation context.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the core insight. As long as you remain inside the same context in which the code was generated, you cannot question it from the outside. Conversely, even the same model can function as a reviewer if it operates in a fresh session, disconnected from the generation context. Being outside the context is precisely what makes it possible to question the answer. &lt;a href="https://claude.com/blog/code-review" rel="noopener noreferrer"&gt;Anthropic's Code Review&lt;/a&gt;, discussed later, is built on this same principle. Whether AI or human — "being outside the generation context" is the condition for effective review.&lt;/p&gt;

&lt;p&gt;"Does this code work?" is not the question. "Was this the right implementation?" "Is this tradeoff really justified?" — raising these questions requires a perspective that differs from the one that produced the code. Focusing resources on review is what makes these questions possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Maybe Not a Coincidence — What Anthropic Is Doing
&lt;/h2&gt;

&lt;p&gt;On March 9, 2026, Anthropic released Code Review for Claude Code.&lt;/p&gt;

&lt;p&gt;The feature automatically dispatches multiple AI agents in parallel whenever a PR is opened, detects bugs, ranks them by severity, and feeds the results back into GitHub. Anthropic applies this system to nearly every PR internally, and the proportion of PRs receiving substantive review comments &lt;a href="https://claude.com/blog/code-review" rel="noopener noreferrer"&gt;jumped from 16% to 54%&lt;/a&gt; as a result.&lt;/p&gt;

&lt;p&gt;What's worth noting is the design philosophy. It has been &lt;a href="https://techcrunch.com/2026/03/09/anthropic-launches-code-review-tool-to-check-flood-of-ai-generated-code/" rel="noopener noreferrer"&gt;reported&lt;/a&gt; that the tool &lt;strong&gt;focuses on logic errors&lt;/strong&gt;, not style or naming conventions. Could this reflect a deliberate choice to leave style correction to the generation side?&lt;/p&gt;

&lt;p&gt;This might be an embodiment of the idea: "rather than loading the generation side with detailed instructions, tighten the exit gate (review)."&lt;/p&gt;




&lt;h2&gt;
  
  
  Overloading CLAUDE.md Backfires
&lt;/h2&gt;

&lt;p&gt;Meanwhile, research is emerging that questions the approach of controlling generation through detailed instructions.&lt;/p&gt;

&lt;p&gt;A paper published on arXiv in February 2026, "Evaluating AGENTS.md" (ETH Zurich et al.), found that loading context files like CLAUDE.md or AGENTS.md with detailed instructions &lt;a href="https://arxiv.org/abs/2602.11988" rel="noopener noreferrer"&gt;&lt;strong&gt;reduces task success rates and increases inference costs by more than 20%&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AI tries to follow instructions, but in doing so generates unnecessary exploration and testing that gets in the way of the actual task. The paper's conclusion is simple: "Unnecessary requirements make tasks harder. Context files should contain only minimal requirements."&lt;/p&gt;

&lt;p&gt;Keeping instructions minimal — this direction may be empirically supported as well.&lt;/p&gt;




&lt;h2&gt;
  
  
  Skills Fall Into the Same Trap as CLAUDE.md
&lt;/h2&gt;

&lt;p&gt;Agent Skills has been gaining attention recently. It's an open format for giving agents procedural knowledge via SKILL.md files, enabling capability expansion.&lt;/p&gt;

&lt;p&gt;There are valid uses. Tasks like generating PPTX files or operating internal proprietary tools — adding capabilities that agents don't have out of the box. In that sense, it differs from CLAUDE.md.&lt;/p&gt;

&lt;p&gt;But misused, it falls into exactly the same trap.&lt;/p&gt;

&lt;p&gt;Consider writing a rule like "always include the time, not just the date, in Rails migration filenames" as a Skill. This is just injecting a corrective instruction as a workaround for unstable output — structurally no different from writing it in CLAUDE.md.&lt;/p&gt;

&lt;p&gt;A practical rule of thumb: &lt;strong&gt;if it can be replaced by a linter or automation tool, it shouldn't be a Skill.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill type&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cannot be replaced by linters etc. (capability expansion)&lt;/td&gt;
&lt;td&gt;Valid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can be replaced by linters etc. (rule correction)&lt;/td&gt;
&lt;td&gt;Same trap as CLAUDE.md&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you find yourself wanting to create a Skill to enforce a rule, first ask whether a linter or automation tool could handle it instead. If it can, it belongs at the exit gate — not injected into the generation side.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scale Benefits Belong on the Review Side
&lt;/h2&gt;

&lt;p&gt;AI scales. That's true for generation too — but I believe scale truly pays off on the review side.&lt;/p&gt;

&lt;p&gt;On the generation side, there's a "lottery approach": keep asking AI to regenerate until you get a good result. More attempts eventually yield better output.&lt;/p&gt;

&lt;p&gt;That said, I personally accept about 90% of AI output as-is. I think it's because I use AI as a collaborative partner rather than giving it detailed instructions. If generation quality is already high enough, there's no need to rely on the lottery approach. If you're going to invest scale somewhere, review is the more rational choice.&lt;/p&gt;

&lt;p&gt;What happens when you invest scale in review? Multiple AIs independently question the same PR. Each raises questions from different angles, catching what others miss. Anyone who has worked in code review knows the feeling of "I want as many eyes on this as possible" — review works the same way. The more reviewers, the lower the probability of something slipping through.&lt;/p&gt;

&lt;p&gt;Anthropic itself has &lt;a href="https://claude.com/blog/code-review" rel="noopener noreferrer"&gt;adopted a design for Code Review that runs multiple agents in parallel, then cross-checks to filter out false positives&lt;/a&gt;. Could this also be seen as an embodiment of the idea that scale belongs on the review side?&lt;/p&gt;

&lt;p&gt;The lottery approach in generation just accumulates cost. Scale in review has intrinsic value: it reduces what gets missed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Human Eyes Still Have Unique Value
&lt;/h2&gt;

&lt;p&gt;Does this mean humans are unnecessary if AI handles review? I don't think so.&lt;/p&gt;

&lt;p&gt;Business judgment, implicit team context, "this is technically correct but is it acceptable for our organization?" — these are areas where humans currently have an edge over AI. That's why there's still good reason for humans to actively invest time in review.&lt;/p&gt;

&lt;p&gt;Scale AI in review, and have humans actively invest in review too. Increasing both kinds of "eyes" raises the quality of the team's answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Process&lt;/th&gt;
&lt;th&gt;Who does it&lt;/th&gt;
&lt;th&gt;What scale means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Generation (opening PRs)&lt;/td&gt;
&lt;td&gt;Let AI go all out&lt;/td&gt;
&lt;td&gt;More output, but no gain in quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review (questioning answers)&lt;/td&gt;
&lt;td&gt;Invest both AI and humans&lt;/td&gt;
&lt;td&gt;Quality improves&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When AI joins the team, the team's job is not to "beat AI at generation." It's to question the answers AI produces — together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Behind Anthropic's release of Code Review lies a real problem: AI is generating so much code that review has become a bottleneck. &lt;a href="https://claude.com/blog/code-review" rel="noopener noreferrer"&gt;Code output per engineer at Anthropic has grown 200% over the past year&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This trend won't stop. Generation will keep increasing, and the importance of review will only grow.&lt;/p&gt;

&lt;p&gt;If you truly trust AI as a teammate, let them go all out producing answers. And as a team, concentrate your resources on questioning those answers.&lt;/p&gt;

&lt;p&gt;Concretely: adopting tools like Code Review, enforcing style conventions with linters and automation, and making sure humans have time for review — these three are a good place to start.&lt;/p&gt;

&lt;p&gt;That's my conclusion for now.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://claude.com/blog/code-review" rel="noopener noreferrer"&gt;Code Review for Claude Code (Anthropic, 2026/03/09)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2602.11988" rel="noopener noreferrer"&gt;Evaluating AGENTS.md: Are Repository-Level Context Files Helpful for Coding Agents? (arXiv:2602.11988)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://agentskills.io/home" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>codereview</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
