feat: Improved runtime compatibility - #42
Merged
Merged
Conversation
The runtime already speaks only the Docker Engine API, which Podman's
`podman system service` serves too, so the whole dev loop (push/build/call
for the Node and Python samples, debug mode, migrations, build abort) works
unchanged by mounting Podman's socket at /var/run/docker.sock. Two places
depended on Docker-specific daemon behaviour and are made engine-neutral:
- Dev-folder probe: Podman's compat API auto-creates a missing bind-mount
source instead of rejecting it, so the create-only probe reported a typo'd
path as present and left a root-owned empty directory on the host. The
probe now binds the host's `/` read-only and stats the candidate through
the archive-stat endpoint (HEAD /containers/{id}/archive), component by
component with symlink following - identical results on Docker and
Podman, no side effects. The same check now also runs at every dev-mount
run start, since Podman would otherwise silently recreate a deleted dev
folder as an empty directory.
- Run telemetry: Podman does not report `system_cpu_usage` on Docker's
scale, so `docker stats`' CPU formula over-reported (~66% for a run
throttled to 0.25 core). CPU percent is now CPU time over wall time,
which agrees with the Docker formula on Docker.
Also: prefix-match the runtime's own container id when checking network
membership (no spurious "already connected" warning on restart), a clearer
unreachable-socket message naming Podman and DOCKER_HOST, e2e helpers that
accept CONTAINER_CLI=podman and a unix:// DOCKER_HOST for the socket mount,
and Podman setup/caveats in README, CLAUDE.MD and the requirements.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Rootless Podman runs the runtime container under slirp4netns/pasta, where attaching to a second network is unsupported, so the apify-api alias never resolved and Actor containers could not reach the API. When the self-attach fails (or the runtime is not in a container at all), every run container now gets an `apify-api -> host-gateway` extra host, landing on the runtime's published port 3333; the startup warning names the cause and the `--network apify-local` alternative. Documents rootless Podman and rootless Docker, including that paths must be readable by the rootless user. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…e support Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Resolves the conflicts with the browser-view feature and carries the Podman work over to the parallel e2e layout: `pullImage` honours CONTAINER_CLI, the Playwright base images are fully qualified, and CI's per-file e2e jobs gain an `engine` dimension that runs every file against rootless Podman as well as Docker. Under rootless Podman browser view needs the runtime started with `--network apify-local` (the console reaches the VNC sidecar over that network); the startup warning and README say so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…n apify-local Under rootless Podman the runtime container cannot join the network, so the console could not reach the VNC sidecar by its address there and the viewer never got the RFB greeting. The sidecar now shares the runtime container's own network namespace in that case, on a port allocated in that namespace, and the console connects to it on 127.0.0.1. The alias route is unchanged whenever it is available. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
A client that polls the build to SUCCEEDED and immediately starts a run against the tag (apify-client's
`build(..., { waitForFinish })` followed by `start()`) could find the tag still missing, because the
status write landed before the tag write; CI hit that window in debug-mode.test.ts. The tag now lands
first and is put back to its previous value if a racing abort refuses the SUCCEEDED write, so an aborted
build still never ends up tagged. Regression test included.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Podman does not create a missing host directory for a bind mount (Docker does), so `podman run` with `-v "$(pwd)/data:/data"` fails on a fresh checkout. Every documented start command now runs `mkdir -p data` first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
A short image name in an Actor Dockerfile (`FROM apify/actor-node:20`) means Docker Hub on the platform and under Docker, but Podman leaves the resolution to the host's registries.conf, which on a stock Debian/Ubuntu install names no unqualified-search registry. `apify push` then fails with "short-name did not resolve to an alias". The build service now rewrites such FROM references to their `docker.io/...` form before handing the source files to the driver, and logs each substitution in the build log. References that already name a registry, `scratch`, build-stage names, and variables are left alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Podman 5 treats `size=` in a local volume's `o` option as a project-quota
request and refuses to create the volume on a filesystem without quota
support ("Volume options size and inodes not supported"), which made every
browser-view run fail at start. The tmpfs volume only ever holds the Xvfb
Unix socket, so the 8 MB cap is dropped; mode 1777 stays.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…en the alias is missing The documented Podman command and the CLI now pre-create the apify-local network and start the runtime container on it (--network apify-local), which is the direct route between Actors and the runtime on every engine, rootless included. A container started that way has no apify-api alias, so the driver now reads its own address on the network and gives Actor containers a hosts entry for it; the DNS alias stays the route when the runtime joined the network itself, and host-gateway remains the fallback when it is not on the network at all. A run the engine refuses to start now writes the reason into the run's log as well as its status message, so `apify call` shows it instead of an empty log. Docs name Podman 4 or newer and the Ubuntu 22.04 CNI config fix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…art containers (Podman 3.4) Ubuntu 22.04's Podman 3.4 writes CNI network configs its own plugins reject, so no container can start on the runtime's apify-local network and every run failed with "CNI network apify-local not found". The driver now retries a container that fails to start there once on the engine's default network; if that works, the network is written off for the rest of the process (one warning naming the cause) and later runs and browser-view sidecars go straight to the default network. Off the network, Actors reach the API through the host at the address the engine itself gives every container (host.containers.internal on Podman, host.docker.internal on Docker Desktop), read from the runtime's own hosts file; host-gateway stays the route only when the file has no such entry (Docker Engine), since Podman before 4.1 rejects the keyword. The documented Podman command no longer needs a pre-created network or --network; docs state Podman 3.4 or newer. Verified against Podman 3.4.4. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…ork view Actors that had to fall back to the engine's default network reached the API through the engine's host entry, which is wrong in two cases seen on Podman 3.4: under rootless slirp4netns that entry is the slirp gateway, which only reaches the host's loopback when the container is started with allow_host_loopback; on a rootful bridge it is the bridge gateway, where the published port need not answer. The driver now reads its own hosts entry, default gateway and interface address and picks: slirp4netns mode with the gateway (rootless 3.x), its own bridge address (rootful bridge), or the engine's host entry / host-gateway otherwise. Podman 3.4 rejects the host-gateway keyword, so it is used only when the hosts file offers nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…art failures Rootless Podman on cgroups v2 refuses to start a container whose limit needs a controller systemd did not delegate to the user; Ubuntu 22.04 delegates memory and pids but not cpu, so once the network fallback got a run past the network error it died on the CPU quota instead. The driver now asks Podman's own info endpoint which controllers it has, leaves out the limits it cannot apply (run and browser-view sidecar alike), and warns once at startup. Docker is unaffected. A run that can start neither on apify-local nor on the default network now reports both failures instead of only the first, which had hidden the real reason. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Podman 3.x is the CNI generation: Ubuntu 22.04's stock config cannot start a container on any user-defined network, rootless cannot attach a running container, and a failed attach on rootful Podman 3.4 was seen to tear down the runtime container's own loopback. The driver now detects Podman 3.x from the API's version components, skips apify-local entirely there, and puts Actors on the default network with the route selection introduced earlier (slirp4netns host loopback rootless, the runtime's own bridge address rootful). Podman 4+ and Docker are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…by name The browser-view sidecar image and the dev-folder probe image were imported or built under a short name and later looked up by that name. Docker and Podman 4 resolve a short name to the local image; Podman 3.x resolves it only through its search registries and reports the locally stored localhost/... image as "image not known", so every browser-view run failed to start there. Actor images are unaffected: they are referenced by id. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…3.4 e2e leg Podman 3.x ignores the separate `tag` parameter of the image import endpoint and stores the browser-view sidecar as `:latest`, so the runtime never found the image it had just imported. The tag now travels inside the repository name (`name:tag`), which Docker, Podman 4 and Podman 3.4 all honour. CI gains an e2e leg on the ubuntu-22.04 runner, whose preinstalled Podman is 3.4.4 - every e2e file, browser view included, now also runs against rootless Podman 3.x. The runtime's own Dockerfile names its base images fully qualified so Podman 3.4 builds it without search registries. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…s an anonymous one)
Podman 3.x's compat API refuses a volume mount without a source ("must set
source volume"), so every dev-folder run failed to start there. The
node_modules volume that preserves the image's dependencies underneath the
bind mount is now a named per-run volume the driver removes with the run
and sweeps by name prefix after a restart; the engine still populates it
from the image on first mount.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Rootless Podman 3.x cannot mount a tmpfs-backed volume ("cannot mount
volumes without root privileges"), so the browser-view sidecar never
started there. The X11 socket volume is now a plain local volume; the
sidecar image carries the socket directory as mode 1777 and its script
chmods it as root before waiting for the Actor's display, so the Actor's
unprivileged Xvfb can still create the socket.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
… the run
Apify's Playwright base images start through ./xvfb-entrypoint.sh inside
the working directory. A dev-folder run bind-mounts the local folder over
that directory, so unless the folder happens to carry the same file the
engine refuses to start the container ("executable file not found"). The
driver now inspects the image's command: a working-directory-relative
entrypoint (or Cmd) the dev folder does not provide is read out of the
image and placed in the container before start, and the run starts
through that copy. Absolute and PATH-resolved commands are untouched, as
is anything the dev folder provides itself.
Covered by unit tests and a new e2e case with a busybox Actor whose
entrypoint lives in its working directory.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…tream The new dev-folder case asserted on `apify call`'s streamed output; for an Actor that exits within milliseconds the CLI's log stream can close before the last lines are flushed, on every engine. The stored run log is what the assertions are about. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…ved entrypoint
Docker and Podman drop the image's Cmd from a create request that overrides
Entrypoint, so the preserved Xvfb wrapper ran with no program to wrap
("xvfb-run: usage error: need a command to run"). The image's Cmd now
travels with the overridden Entrypoint.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
The browser-view samples crawled crawlee.dev; from CI runners that navigation intermittently timed out on every engine, failing one of the two browser-view tests in roughly every other run. The Actors now start from the runtime's console at http://apify-api:3000/, reachable from every Actor container on every engine through the same route as the API, with enough interlinked pages for the counts the tests assert. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
The browser-view Actors must crawl a real external site: routing them to the runtime's own console would let a network-setup mismatch on the private route go unnoticed. This reverts commit 46cc7b56ab2bbc0f1b78ea3b5c4e11f1a0e1c8be. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Their Actors crawl a real external site on purpose, and CI runners occasionally see that site's navigations time out; a single retry keeps a slow spell from failing the job while a reproducible defect still does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
The retry-on-default-network path in startRun and the sidecar launch wrapper in startBrowserViewer were an intermediate attempt from before Podman 3.x detection landed in init(). Since Podman 3.x now decides at startup that Actors run on the engine's default network, the runtime fallback never fires; drop it with its tests, and tidy the driver header and docs to describe only the final behaviour. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
…n run
The retry added to the browser-view cases could never pass: the retried
attempt pushed the Actor again and the CLI refused ("already exists ...
newer changes than your local copy"). The suite now pushes in beforeAll
and both cases, retried or not, run the Actor that push produced.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZhuUP13NByepkP4n3sg3P
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Make it compatible with rootful or rootless Podman or Docker (4 supported variants)