feat(podman): auto-detect container engine and support Podman#49
feat(podman): auto-detect container engine and support Podman#49wmeints wants to merge 5 commits into
Conversation
Follow-up fix: engine detection through the
|
Follow-up fix: operator locked out of the web UI on PodmanOn rootless Podman the web UI returned Cause: Podman's rootless port forwarder (pasta) delivers host Fix (commit ed3d824): on Podman, start huddle with the egress network ( Verified on rootless Podman: operator Minor note for reviewers: as with Docker's bridge, other containers the user runs on the default |
0f9fb0e to
77c0b93
Compare
Heads-up: fixed a Podman DNS regression that broke egress (pushed as c0b6e5d)While testing this branch I hit a 502 from Root causeThe gateway attaches to the The gateway image is Alpine (musl), and musl's resolver queries all nameservers in parallel and uses the first reply. The on-host aardvark returns its NXDOMAIN almost instantly and wins the race against the slower real egress resolver → every external lookup fails with Confirmed against the live gateway's own audit log (all 502s had
Fix (
|
Detect Docker vs Podman across the CLI, gateway, and PowerShell manager, and handle Podman's socket mapping and SELinux/bind-source differences so `huddle init` and devcontainers work on Podman as well as Docker. - runtime.ts: add isRemote (engine-in-VM) and securityOpts to ContainerRuntime - init.ts: pre-create the socket dir in the machine VM for Podman (it won't auto-create a bind source), pass --security-opt label=disable and HUDDLE_RUNTIME to the huddle container - gateway/docker.ts: add SecurityOpt label=disable to devcontainers when HUDDLE_RUNTIME=podman so they can reach the SELinux-labeled proxy socket - huddle.ps1: resolve the runtime once (mirrors the CLI) and route every docker call through it; export HUDDLE_RUNTIME for the delegated `huddle init` - restore the executable bit on the CLI bin entrypoint (cli/dist/index.js) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Many Podman setups symlink or shim `docker` to Podman (podman-docker), and
Podman emulates `docker --version`, so name-based detection wrongly picked
"docker" — misconfiguring the socket path, network, and skipping
label=disable, which made `huddle init` fail.
Probe the real engine instead: Podman's `info` exposes Host.ServiceIsRemote
while Docker's schema does not. Use it (via `<cmd> info --format
{{.Host.ServiceIsRemote}}`) in both the CLI runtime resolver and huddle.ps1
so the engine behind the `docker` command is identified correctly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Under rootless Podman (pasta), host port-forward traffic enters the huddle container via its PRIMARY interface and is sourced from that subnet. With devcontainer-net primary, the operator's browser appeared to originate from the devcontainer subnet, so the source-IP gate rejected the UI with "endpoint not allowed from devcontainer network". Start the huddle container on the egress network (runtime.defaultNetwork) as primary and attach devcontainer-net afterwards, so host traffic arrives from a non-blocked subnet while devcontainers stay blocked. Podman-only; Docker forwards via its own proxy (source = loopback/bridge-gw, independent of the primary network) and keeps the existing ordering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tart
Podman phrases an already-connected network differently than Docker
('network is already connected' vs 'already exists in network'), so the
guard in createAndStartContainer re-threw and aborted the devcontainer
start with a 403. Broaden the guard to match both.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…NS works The gateway attaches to the --internal devcontainer networks; Podman then adds those networks' aardvark-dns (which answers NXDOMAIN for external names) to the gateway's /etc/resolv.conf. On Alpine/musl the resolver queries all nameservers in parallel and uses the fastest reply, so the on-host aardvark's instant NXDOMAIN wins the race and every external lookup fails with ENOTFOUND — api.anthropic.com and other upstreams then return 502 through the proxy. Add dns-egress.ts: probe each nameserver and remove the ones that cannot resolve externally (reordering does not help with musl's parallel queries). It runs at startup, over a short settling window (to catch the devcontainer-net connect that `huddle init` performs after the gateway has started), and after every gateway network connect/disconnect. Fail-safe: leaves resolv.conf untouched when no resolver works. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
c0b6e5d to
cb10aec
Compare
|
Ik sluit deze PR omdat de ideeën uit deze branch zijn samengevoegd met #29, #49 en #51 in één consolidatiebranch gekoppeld aan #47: experiment/47-podman-support-consolidated. Zou je willen testen of dit nu werkt in jouw setup? of Dit installeert de CLI en gateway image van dit experiment, zodat je end to end kunt testen op je eigen Podman host of VM. Zodra je klaar bent, kun je terugschakelen naar de stabiele versie met: Laat gerust weten als je nog problemen of onverwacht gedrag tegenkomt. Dan lossen we dat op in de experiment branch voordat alles naar main gaat. |
Why
Huddle was hardwired to Docker (Docker Desktop on Windows). Teams that standardise on Podman couldn't run it:
huddle initfailed and devcontainers couldn't reach the Docker socket proxy. This change makes Huddle detect the container engine automatically and adapt its socket mapping and container configuration accordingly, so it starts and runs on Podman as well as Docker. Scope is intentionally limited to Docker + Podman.What changed
The gateway stays engine-agnostic — it drives the engine through the Docker-compatible REST API on
/var/run/docker.sock, which Podman also serves. The real differences are around socket mapping and SELinux, handled at the edges:cli/src/runtime.ts—ContainerRuntimegains:isRemote— whether the engine runs in a VM (Podman machine / Docker Desktop). Detected viapodman info --format {{.Host.ServiceIsRemote}}; for Docker,platform !== 'linux'.securityOpts—['label=disable']for Podman, empty for Docker.cli/src/init.tspodman machine ssh "mkdir -p /tmp/dc-sockets") — Podman does not auto-create a missing bind source the way Docker Desktop does, and native Linux stillmkdirs locally.--security-opt label=disableand-e HUDDLE_RUNTIME=<engine>to the huddle container.gateway/src/docker.ts— whenHUDDLE_RUNTIME=podman, addsSecurityOpt: ['label=disable']to each devcontainer'sHostConfigso it may access the SELinux-labeled per-container proxy socket.huddle.ps1— resolves the engine once (same precedence as the CLI: explicitHUDDLE_RUNTIME, then Docker, then Podman), routes every directdockercall through& $RUNTIME, and exportsHUDDLE_RUNTIMEso the delegatedhuddle inituses the same engine. On Windows + Docker Desktop nothing changes.cli/dist/index.js— restore the executable bit on the CLIbinentrypoint (was committed as non-executable, causing "permission denied" after a global install).Important parts to review
gateway/src/docker.ts—label=disableon devcontainers. This relaxes SELinux confinement on devcontainers when on Podman. It is required for them to reach the proxy socket (verified: a confined client gets "Permission denied"). The huddle container itself already getslabel=disablefrom the CLI. Note the child-container guard insocket-proxy.tsstill rejectslabel=disablefrom containers spawned inside a devcontainer — that path is unchanged.cli/src/init.ts— socket-dir precreation for remote Podman. Thepodman machine sshcall assumes the default machine. Worth a look for multi-machine setups.cli/src/runtime.ts—isRemotedetection and the Dockerplatform !== 'linux'heuristic.huddle.ps1— runtime resolution +HUDDLE_RUNTIMEexport, ensuring the script and the delegated CLI agree on the engine when both are installed.Risks
securityOptsempty, noSecurityOptinjected, existing socket-dir behavior preserved for Windows/Linux).huddle.ps1picks Docker first, so the Windows/Docker Desktop flow is unchanged.label=disablereduces SELinux isolation for the huddle container and Podman devcontainers. Acceptable given the huddle container already has full engine access via the mounted socket, but it is a real posture change on SELinux-enforcing hosts./tmp/dc-socketslives on VM-local tmpfs (shared correctly between huddle and devcontainers). Untested against remote/rootful Podman over SSH or multi-machine configs.label=disablebehavior was verified in isolation and via the gateway's own startup socket calls.Verification
huddle init --runtime podmanon a Podman machine: huddle container comes up ondevcontainer-net,podman, correctSecurityOpt/env/mounts, gateway boots with no socket errors, and a request over/var/run/docker.sockfrom inside returns HTTP 200.huddle.ps1end-to-end on Podman: resolvespodman, delegateshuddle initwithout error, status shows[ON] Huddle draait.🤖 Generated with Claude Code