Repository navigation
Feat/runtime detection to add podman support (and base for others) - #18
Conversation
…docker
Every script that ran a container said `docker` outright, so the kit only
ran where Docker was installed — even though nothing it does is
Docker-specific. scripts/_runtime.sh resolves the CLI once into $RUNTIME
and every call site reads that instead.
Supported: docker, podman, nerdctl, finch. The contract is narrow and
stated at the top of the file — a client CLI speaking Docker's command
grammar (run/pull/build/info/version/image inspect) that also ships a
`compose` subcommand, since the whole stack is driven through Compose.
Anything meeting it can be added to _RUNTIME_SUPPORTED without touching
another line.
docker is first in the precedence list deliberately: a machine that has
always had Docker behaves exactly as it did before this file existed.
RUNTIME is read from the environment or .env rather than installed as a
`docker` shim on PATH — a shim is per-machine, invisible in the repo, and
changes what every other tool on the box does too. This knob is
per-checkout and sits next to BUILDER_TAG and DUPLO_TARGET where a reader
of these scripts already looks.
runc is rejected with an explanation rather than accepted. It is a
low-level OCI runtime that executes an already-unpacked bundle by path and
cannot resolve an image name, so `runc run busybox:1.36` can never work;
it is the layer these CLIs call underneath. Choosing it is a different
knob from choosing the client.
Two things podman forced that are not cosmetic:
docker-compose.yml the builder image default was a nested
${BUILDER_IMAGE:-…:${BUILDER_TAG:-latest}}.
podman-compose 1.5 stops at the first '}' and
appends the remainder literally, giving an "invalid
reference format" even when BUILDER_IMAGE is set.
Collapsed to one level. Nothing is lost in any
scripted path: builder_resolve_image resolves
BUILDER_TAG into BUILDER_IMAGE and exports it before
compose is ever invoked, so the default is reached
only by a hand-run `compose run`.
_builder.sh the builder's uid/gid is no longer always `id -u`.
Under rootless podman the caller is already mapped
to uid 0 inside the user namespace, so 0:0 is what
yields caller-owned files while a real uid maps to
an unwritable subuid. runtime_builder_ids picks per
runtime.
tests/test-runtime.sh covers resolution, rejection, value cleaning and the
rootless-podman ids, and sweeps the kit for scripts that still invoke a
runtime by name or use it without resolving first. 16 tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
run.sh now refuses to start when the podman machine is too small to build
in, instead of letting an undersized VM poison every later extension build.
The failure this pre-empts is opaque: the VM's memory is fixed at create
time and shared by the six-container stack AND every build, and the
Angular Native-Federation build is what exhausts it first. The OOM kill
surfaces as a bare `Killed`, ~100 lines of Go "all goroutines are asleep
- deadlock!" from esbuild, and finally `exit status 137` — the only part
that means "out of memory", and the last line anyone reads.
scripts/_runtime.sh runtime_machine_check, alongside the other runtime_*
helpers. Reads the CONFIGURED size via `podman
machine list --format json` (answers for a stopped
machine too, and is the number `podman machine set`
changes). Memory below the floor is a hard failure;
a low CPU count only warns. Parses with python3,
which run.sh already hard-requires — jq would add an
undeclared dependency to the scripts that source
this file. Every read or parse failure returns 0:
this exists to give a better message than exit 137,
never to become a new way for the kit to refuse to
start. No-op on docker, nerdctl/finch, and
machine-less Linux podman.
run.sh calls it after the runtime is resolved.
.env.example documents PODMAN_MIN_MEMORY_MIB / PODMAN_MIN_CPUS.
The 6 GiB floor is a judgement call, not a measurement, and the code says
so: 2 GiB reliably OOMs the frontend build, 8 GiB completes comfortably,
nothing between was tested. The remediation command derives its values
from max(current, floor), so a raised floor is honoured and a 6-CPU
machine short on memory is never told to downgrade itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On Apple Silicon an amd64 studio image runs emulated, and podman does NOT
enable Rosetta by default — `[machine] rosetta = true` in containers.conf is
required, and an absent file behaves exactly like an explicit `false`. Without
it amd64 falls through to QEMU user-mode, which cannot run the studio's .NET
runtime.
Every layer of that failure lies. QEMU aborts inside MapControllers() with
SIGABRT; the container still reports "Up" because the crash does not take PID 1
down promptly; /healthz never answers; and run.sh then spends ~4.5 minutes on a
dot loop before exiting with "Login failed" — a message about credentials, for
a problem that has nothing to do with them.
runtime_rosetta_check replaces all of that with one accurate message. It is
scoped to the only combination that matters, and is a no-op on docker (Docker
Desktop ships Rosetta itself), on an amd64 host, and on an arm64 studio image.
The probe reads the machine's binfmt_misc handler list, NOT `podman machine
inspect`: inspect reports the flag the VM was last STARTED with and goes stale
the moment containers.conf changes, whereas the handler either exists in the
running kernel or it does not. A stopped or unreachable machine cannot be
judged, so it returns "unknown" and the check stays silent — like
runtime_machine_check, this exists to replace a baffling failure with a clear
one, never to become a new way for the kit to refuse to start.
THE REMEDIATION IS READ FROM THE EXISTING CONFIG, and that is not polish. The
obvious advice — appending `[machine]\nrosetta = true` to containers.conf — is
actively harmful on any machine that already has one. It creates a second
`[machine]` table, TOML rejects the duplicate key, and podman then refuses to
run at all:
Failed to obtain podman configuration: parsing containers.conf:
toml: line 11: Key 'machine' has already been defined.
That is strictly worse than the missing Rosetta it was meant to fix. So
_runtime_rosetta_conf_state reads the file podman will actually read (honouring
CONTAINERS_CONF) and the message branches five ways: create it, add the section,
add the key under the existing section, flip `false` to `true`, or — the common
case for anyone who configured this once and later recreated their VM — "your
config is already correct, the machine simply has not been restarted since".
The impure parts (_runtime_host_arch, runtime_rosetta_active) are separate
functions so the policy is testable with no podman, no VM and no Apple Silicon
host; the suite still runs identically on a docker laptop and in CI. Sixteen new
tests. Each guard was mutation-tested — removed, confirmed a named test fails,
restored — because three of the message assertions were initially vacuous: under
this suite's `set -o pipefail`, `cmd | grep -q` inverts, since grep -q exits on
first match, SIGPIPEs the writer, and the pipeline reports the writer's failure.
They capture into a variable now, with a comment so it is not reintroduced.
Also in this commit:
- finch is TEMPORARILY de-listed from _RUNTIME_SUPPORTED pending validation. It
is no longer auto-detected and RUNTIME=finch is rejected. This is a
claim-reduction, not a removal: every finch code path is retained and still
correct, including builder_run_direct and the probe fix below. Re-enable by
putting `finch` back in the array.
- builder_probe_runtime asks for `{{json .Server}}` rather than
`{{.Server.Version}}`. finch's server struct has no Version field, so the
narrower template died in the template engine — indistinguishable from an
unreachable runtime, which reported a healthy finch as "no container runtime
is available" and refused the build.
- builder_run_direct runs the builder image directly for CLIs whose `compose`
cannot express the invocation, with the evidence for each of the three
reasons recorded at the function.
- nerdctl removed from the remaining user-facing lists, matching the earlier
removal from _RUNTIME_SUPPORTED.
- docs: configuration.md gains the Container runtime section it was missing
despite promising every .env variable; prerequisites.md gains the Apple
Silicon podman setup (Rosetta, and the 6144 MiB floor vs init's 2048 default);
troubleshooting.md gains the full symptom-to-fix entry. All three record that
auto-detection tests presence only, with no daemon check, so installing Docker
silently takes precedence over an installed podman, and all three warn never
to append a second [machine] table.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rosetta is an applehv feature. podman 6 defaults to libkrun on Apple Silicon, and LibKrunStubber.GetRosetta returns false unconditionally, so `rosetta = true` under libkrun is read and discarded — leaving no trace but the missing binfmt handler. runtime_rosetta_check saw that handler was missing and blamed the config, telling people their file was already correct and the machine just needed restarting. It could never come true, and they recreated the same VM until they gave up. The provider now leads the diagnosis, resolved from the running machine's VMType first (podman machine init --provider does not write containers.conf, so the file describes the NEXT machine, not this one), then the environment, then the config. The remediation splits the two lifetimes that were previously conflated: the provider is fixed at init, so changing it means recreating the VM; the rosetta key is re-read by applehv's StartVM on every start, so on applehv a stop/start really is enough. The old footer promised the latter under every branch. Verified on real hardware both ways: a libkrun machine is told to switch provider and recreate; a rebuilt applehv machine with rosetta = true passes silently, and an amd64 studio image serves /healthz 200 under Rosetta. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Reviewed this end to end, including running the suites locally and rebasing onto current This is unusually careful work. Two things worth naming because a quick review would skip them: splitting Four blockers. 1.
|
runtime_resolve reads RUNTIME from .env and then memoises its answer in _RUNTIME_RESOLVED for the rest of the run. It ran at run.sh:129, but .env was not created until run.sh:162 — so on a first run there was nothing to read, auto-detection picked docker on presence, and that choice was locked in for both podman preflights, `compose pull` and `compose up`. The effect was the feature's headline promise failing on exactly the first run, on any machine with Docker installed alongside podman: RUNTIME=podman in .env was ignored, and so was shipping it in .env.example, since the copy happened after resolution. Creating .env needs neither python3 nor $RUNTIME, so it moves above the prerequisite block with a comment recording why the order matters. Reported in review of duplocloud#18. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The conf scan anchored on `^[[:space:]]*\[machine\][[:space:]]*$`, so a valid TOML header with a
trailing comment did not match:
[containers]
[machine] # my settings
rosetta = true
That file reported state `no-machine`, whose remediation tells the user to ADD a [machine] section —
producing the `Key 'machine' has already been defined` breakage the function's own header comment
exists to prevent, and doing it to a user whose config was already correct. Strictly worse than the
missing Rosetta it set out to diagnose.
Allows an optional comment after the header in all three places that scan for it (two awk block
extractions and the grep that distinguishes no-key from no-machine), plus a fixture case in the conf
helper. Verified against a clean-header control, which still reports `enabled`, so the trailing
comment was the only variable.
Reported in review of duplocloud#18.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Linux is not supported right now, so leading with `apt install podman-compose` sends the large majority of users to a command that does not exist on their machine. Both places this PR added it (the missing-compose diagnostic in run.sh and the Apple Silicon setup in prerequisites.md) now name brew only. The pre-existing python3 apt/dnf hint at run.sh:123 is left alone: it predates this branch, so the accompanying test is scoped to the podman-compose guidance rather than every install hint in the kit. Reported in review of duplocloud#18. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The error:runtime branch this PR introduced used one message for three different situations, and got
two of them wrong:
ERROR: no container runtime is available and the local toolchain is incomplete.
Install docker or podman (with a 'compose' subcommand) ...
Anyone who had simply not started Docker Desktop was told to install docker. That is the most common
way to reach this branch — Docker Desktop does not auto-start unless you enable it — and the advice
does not even mention starting anything. Worse on a box with both runtimes: detection is presence-based
and docker-first, so a stopped docker beside a healthy podman resolves to docker, refuses to build, and
recommends installing the podman that is already running. Hit in practice on a libkrun podman box.
Detection stays presence-only — a daemon probe on every run is not worth the second — so the fix is to
make the failure self-diagnosing rather than to change what gets picked:
runtime_alternatives the supported runtimes OTHER than the resolved one that actually answer, over
a _runtime_answers seam so the policy is testable with no runtime installed.
builder_runtime_message pure formatter, four cases. Nothing on PATH -> install (unchanged, it was
right). Present but dead -> start it, with the per-runtime command. Another
one answers -> use it, with the exact RUNTIME=<x> invocation. Explicitly
requested -> same, but never phrased as though they had not chosen it.
run.sh gets the same treatment: its not-ready note now names a working alternative, and it announces
the runtime it picked next to "Pulling images" rather than leaving a presence-based choice invisible
until it breaks — the non-blocking item from the review.
Tests: 55 (was 45). runtime_alternatives is mutation-tested in both directions: including the resolved
runtime, or skipping the reachability probe, each makes specific tests fail.
Reported in review of duplocloud#18.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ving it to be rediscovered
Running the stack under one runtime and building under another produces a warning that looks like a
broken build and is not:
WARNING: no running duplo-ai-studio in this compose project … will try the published
host port instead: http://host.docker.internal:60031
Compose scopes services per project per runtime, so a build under docker cannot see a studio started
under podman. The fallback reaches the studio through its published host port — the same one the browser
uses — so the SDK fetch succeeds and the bundle is correct. Easy to hit, because detection is
presence-based and docker-first: `RUNTIME=podman ./run.sh` for the stack, then a build with no RUNTIME
set, and the two are on different runtimes without either command being wrong.
The part worth writing down is the confirmation step, because its result reads as a failure: the SDK feed
is authenticated, so probing it unauthenticated from inside the builder returns **401 — which proves the
route works**. Anyone checking this without being told will read the 401 as the fault and go looking for
a token problem that does not exist.
Also makes the neighbouring entry's `docker compose ps` runtime-agnostic, since it sits two paragraphs
from an entry about runtimes not matching.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…an-compose The earlier fix for the apt/dnf guidance overshot: it replaced one wrong package manager with one package, and `podman-compose` is not the only provider podman will delegate to. A `docker-compose` binary satisfies `podman compose` just as well, and Podman Desktop installs one during its own setup. Verified on a machine with no docker installed at all: `podman-compose` absent, `/usr/local/bin/docker-compose` present, and `podman compose version` answering — so the previous text told that user to install a second provider for a prerequisite they had already met, under a different name. Both places now describe the requirement rather than a package, and point at the check `run.sh` actually runs (`podman compose version`), so the advice stays true whichever provider is installed. The accompanying test asserts that anywhere podman-compose is named, an alternative route is named too — the failure mode being fixed is naming exactly one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The alternative-naming added for the build failure and the readiness warning skipped the one preflight most likely to hit it. Reported from real use: `.env` pinned RUNTIME=podman, podman's socket had broken, docker was up with Compose v2 — and run.sh printed install advice for both runtimes without mentioning that docker would have worked immediately. Every line of that advice was correct and none of it was relevant, which is the same failure the error:runtime message had: reciting prerequisites at someone who already meets them. runtime_alternatives_compose is deliberately narrower than runtime_alternatives. A runtime can be reachable and still have no compose subcommand, and offering that one as the way out of a compose failure sends the person in a circle — so both conditions are required, each behind its own probe (_runtime_answers, _runtime_has_compose) so the policy is testable with neither runtime installed. The message also names the pin as the likely cause, because that is what it was: auto-detection would have picked docker, and a RUNTIME in .env silently outranks it. Tests: 60 (was 56). Both conditions are mutation-tested — dropping the compose requirement or the resolved-runtime exclusion each makes a specific test fail. Verified live on the machine that reported it: with RUNTIME=podman forced and podman broken, it names docker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… command line Every message added here led with `RUNTIME=<x> ./some-script`, which is advice that solves one command and breaks the next. run.sh, build-extension.sh, build-all.sh, logs.sh and stop.sh each source _runtime.sh and resolve independently, so a per-process override does not survive to the following command — and a stack started under one runtime with a build run under another is exactly the cross-runtime condition documented two entries earlier in troubleshooting.md. The guidance was manufacturing the next bug. All three sites now name `.env`, which is the only place every script looks, and say why the command line will not do rather than leaving it as an unexplained preference. They also distinguish the two cases with runtime_requested, because the instruction differs: with RUNTIME already pinned the fix is to change that pin (and the message names its current value, since the pin outranking auto-detection is usually why the person is reading this at all); with nothing pinned the fix is to add it. The one remaining mention of a command-line RUNTIME is in troubleshooting.md, where it explains how a reader ended up with a build and a stack on different runtimes. That is a description of a cause, not a recommendation, and it stays. Tests: 62 (was 60). One asserts no script recommends a per-process override; the other asserts the messages say why .env, so the reasoning cannot be dropped in a later edit while the wording survives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks — this was an unusually useful review. All four blockers are addressed; blocker 4 is stacked separately as you offered. This PR is now 12 commits. 1 — 2 — 3 — apt/dnf guidance. Dropped. I first replaced it with The pre-existing apt/dnf hint for python3 at 4 — retire Rosetta, unpin amd64. Done, stacked in a follow-up PR. I verified your premise independently: the studio tag currently pinned is already an OCI index carrying both arches, so this is not forward-looking — the pin was forcing QEMU today when a native arm64 layer already existed. Three commits, parts 1–3 atomic as you specified. The migration is deliberately conservative: it removes the line only when The doc sweep was wider than the review listed: Three further findings, downstream of your blocker-4 framingRetiring that machinery meant looking at what the kit says when a runtime is not usable, and the messages turned out wrong in the same way three times over — reciting prerequisites at someone who already meets them:
Also added your non-blocking runtime announcement ( Not doneFour of your non-blocking items: the One correction to this PR's own bodyIt says the 62 runtime tests (was 39), 121 across the suite, all passing. Merges clean into |
Heads-up on what follows thisBlocker 4 is done and pushed, but not opened as a PR yet. Basing it on Reviewable now without a PR, if you want it early: duplo-darren/devkit-fork@feat/runtime-detection...feat/studio-platform-unpin That compare shows exactly the three commits and nothing from this PR:
The migration is the part worth your eye, since it writes to a user's 65 runtime tests there (62 here). One thing that will look odd across the two PRs: it deletes the function fixed by "match a |
Every studio release now publishes amd64 and arm64, so pinning the platform is no longer correct: the
pin at docker-compose.yml:61 was the only one in the stack, and on Apple Silicon it forced QEMU
emulation when a native arm64 layer was already available in the manifest. Verified against the
currently pinned tag, which is an OCI index carrying both arches.
Four changes that have to land together, because any subset is broken:
1. docker-compose.yml — `platform: ${STUDIO_PLATFORM:-}`, the idiom BUILDER_PLATFORM already uses
two lines down. No pin, not linux/arm64: a hard arm64 value would break amd64 Linux.
2. .env.example — the live pin becomes a commented-out key. Unset is now the correct value.
3. scripts/_runtime.sh — runtime_rosetta_check treated an unset STUDIO_PLATFORM as amd64 to match
the old compose default. Left alone, dropping the pin would make it hard-exit with a false
positive on every Apple Silicon podman user. Unset now returns 0.
4. run.sh — a one-time migration removes the stale pin from existing .env files. The adoption
mechanism cannot do this itself: it skips keys the example ships blank, so blanking the key would
leave every existing user on amd64 forever.
The migration is deliberately conservative. It removes the line only when .env still holds
linux/amd64 AND .env.defaults agrees that was the last-applied default — i.e. the user never touched
it. Anything else is left exactly as found, and runtime_rosetta_check explains the situation instead
of overriding a deliberate choice. It takes its paths as arguments so the policy is testable without a
real .env, and it is silent unless it changes something.
Tests: 52 (was 45). Both migration guards were mutation-tested — loosening either the value check or
the lock check makes a specific test fail, so they are not passing by accident.
Reported in review of duplocloud#18.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With the studio platform unpinned, the advice this subsystem generated stops being the advice we want to give. Once an arm64 layer resolves natively, the correct remediation is "remove the stale pin", not a walk through containers.conf — so ~150 lines that were correct are now actively misleading, pointing Apple Silicon users at Rosetta they no longer need. Deleted: _runtime_rosetta_conf_state, _runtime_machine_vmtype, _runtime_machine_conf_provider, _runtime_machine_provider, and the six-branch remediation with its libkrun/applehv provider diagnosis. scripts/_runtime.sh drops from 651 lines to 489. runtime_rosetta_check keeps its purpose and loses its bulk. It fires only on an EXPLICIT amd64 STUDIO_PLATFORM, and says two things: remove the pin (the stale-.env case, which is nearly all of them), and — because unsetting cannot help someone whose registry genuinely carries amd64 only — that such a tag does still need Rosetta, via Docker Desktop or an applehv machine. That second branch is why the check survives at all rather than going with the machinery. Retained: _runtime_host_arch and runtime_rosetta_active, the two impure seams that let the policy be tested without a VM. runtime_machine_check is the undersized-machine preflight and is unrelated to any of this; it is untouched. Tests: 41 (was 52). The 16 conf-state, provider and remediation-message cases go with the code they covered; 5 new ones pin the retained check's two branches and assert the deleted helpers are actually gone rather than merely unreferenced. The pipefail/`grep -q` warning is preserved on the new helper — it lived in a block this commit deletes, and the bug class it documents outlives the tests. Note: this deletes the function fixed in the [machine]-trailing-comment commit on duplocloud#18. That fix still matters — it ships first and protects anyone who hits it in the interim. Reported in review of duplocloud#18. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… platform
The docs described a world where the studio image was amd64-only and Rosetta was a setup step. Both are
now wrong, and wrong in the expensive direction: they send Apple Silicon users to configure Rosetta they
do not need, for emulation that no longer happens.
troubleshooting.md The "hangs on Waiting for studio" entry goes from 206 lines to 71, and from 45
Rosetta/libkrun mentions to 4. The symptom, the misleading "Login failed" and the
QEMU/.NET explanation are all kept — they are still exactly what you see. What
goes is the containers.conf walkthrough, the applehv measurement table, the
libkrun provider deep-dive and the rosetta-activation.service forensics. The fix
is now "remove the pin", with the single-arch-registry case noted after it.
prerequisites.md Apple Silicon setup goes from three requirements to one: size the machine. Says
plainly that Rosetta is not required.
configuration.md STUDIO_PLATFORM's default is *(unset)*, not linux/amd64.
faq.md Apple Silicon runs natively rather than emulated.
upgrading.md STUDIO_PLATFORM dropped from the tracked-keys list, with a note that it stays
tracked in code but is skipped while the example ships no value.
run.sh The preflight's comment described the old trigger; it now says the check fires
only on an explicit amd64 pin.
Four tests keep the docs honest rather than trusting this sweep: no libkrun/applehv walkthrough survives
anywhere under docs/, nothing still claims the image is amd64-only, configuration.md documents
unset-means-native, and nothing tells the user to pin linux/arm64 — which is now the thing that resolves
by itself. Tests: 45.
Reported in review of duplocloud#18.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What this changes
The kit assumed Docker.
$RUNTIME—dockerorpodman, resolved once inscripts/_runtime.sh— now replaces every hard-codeddockercall acrossrun.sh,stop.sh,logs.shandscripts/, and two preflights catch the podmanmisconfigurations that otherwise fail late and blame the wrong thing:
exit status 137at the foot of a Go stack trace.It aborts at startup, the container still reports
Up,/healthznever answers, andrun.shspends ~4.5 minutes on a dot loop before exiting withLogin failed— amessage about credentials for a problem that has nothing to do with them.
Both replace a baffling failure with an accurate one, both are no-ops on docker, and
both stay silent when they cannot determine the answer rather than guessing.
Rosetta is a provider question first
podman does not enable Rosetta by default, and an absent
containers.confbehavesexactly like
rosetta = false. But the trap that costs the most time is one level up:Rosetta is an applehv feature, and podman 6 defaults to
libkrunon Apple Silicon.Upstream's
LibKrunStubber.GetRosettareturns false unconditionally, sorosetta = trueunder libkrun is read and discarded, leaving no trace anywhere except the missing binfmt
handler. The config looks right,
podman machine inspectkeeps sayingRosetta: false,and recreating the machine changes nothing.
The check therefore leads on the provider, resolved from the running machine's
VMType first —
podman machine init --provider applehvdoes not writecontainers.conf, so the file describes the next machine, not the one you aretalking to — then the environment, then the config.
The remediation keeps two lifetimes apart, because conflating them is what sends people
in circles:
providerpodman machine initrosettaStartVMevery startThe Rosetta remediation is read from your existing config, which is not polish:
appending
[machine]\nrosetta = trueto acontainers.confthat already has thattable is a TOML duplicate-key error, after which podman refuses to run at all —
strictly worse than the missing Rosetta. The check reads the file podman will read and
prints one of six specific instructions: the provider case above, or one of five conf
states, including "your config is already correct, the machine just needs restarting"
— which is now correctly scoped to applehv, where it is true.
finch is temporarily de-listed pending validation — not auto-detected,
RUNTIME=finchrejected. A claim-reduction, not a removal: every finch code path isretained and correct (
builder_run_direct, and the{{json .Server}}probe fix for aserver struct with no
.Server.Version). Re-enable by puttingfinchback in_RUNTIME_SUPPORTED.Verified on real hardware, both directions
recreate sequence, exits 1.
rosetta = true) —podman machine inspectreportsRosetta: true, the VM'sbinfmt_mischasrosettaand noqemu-x86_64, bothpreflights exit 0 silently, and the amd64 studio image serves
/healthz200 withuname -m=x86_64inside the container and no QEMU abort in the log.39 tests in
tests/test-runtime.sh, all static or pure-function, so the suite runs thesame on a docker laptop, a podman box and CI.
Docs
configuration.mdgains the Container runtime section it was missing despitepromising every
.envvariable;prerequisites.mdgains the Apple Silicon podman setup— now three steps, provider first;
troubleshooting.mdgains the full symptom-to-fixentry, with libkrun called out ahead of the applehv-measured Rosetta table. All three
record that auto-detection tests presence only, with no daemon check, so installing
Docker silently takes precedence over an installed podman.
Note for reviewers
On rootless podman the UI container fails to start —
nginx: bind() to 0.0.0.0:80 failed (13: Permission denied)— because it has noCAP_NET_BIND_SERVICE. That is fixed by#17 (and #19 stacked on it), not here. This PR makes podman work; the UI needs #17 to
come up on it.
4 commits · 14 files · +1624 / −119 · new:
scripts/_runtime.sh,tests/test-runtime.sh