feat: Add local Actor runtime (`apify runtime`) by Pijukatel · Pull Request #1496 · apify/apify-cli · GitHub
Skip to content

feat: Add local Actor runtime (apify runtime) - #1496

Draft
Pijukatel wants to merge 19 commits into
masterfrom
actor-runtime
Draft

Pijukatel wants to merge 19 commits into
masterfrom
actor-runtime

Conversation

@Pijukatel

Copy link
Copy Markdown
Contributor

Merges the actor-runtime branch (published so far on the runtime npm dist-tag) into master.

Adds apify runtime — a command group that installs, starts, stops and connects the CLI to the local Actor runtime (apify/actor-runtime, a self-contained local Apify platform running as a container on Docker or Podman). 44 files, +3692/−64, no dependency or lockfile changes.

Most of the diff is isolated and inert outside the new namespace

These paths did not exist on master. Nothing on master imports them, and none of their code runs unless a user invokes apify runtime ...:

  • src/commands/runtime/* — install, start, stop, status, connect, disconnect, skill (~740 lines)
  • src/lib/runtime/* — container engine detection and docker/podman invocation, runtime config in ~/.apify/actor-runtime/config.json, dev-folder and monorepo source-context push, skill extraction (~1060 lines)
  • test/local/lib/runtime-*.test.ts, test/local/commands/push-*.test.ts — their tests (~1120 lines)
  • docs/reference.md — regenerated by pnpm run update-docs

The runtime is opt-in: apify runtime connect writes connected: true to the config file above, and every change in shared code below is keyed off that flag, APIFY_CLIENT_BASE_URL, or a non-apify.com API host. A user who never runs apify runtime sees no behavior change.

Changes outside that namespace — please review these

Shared source code

  • src/lib/auth.ts, src/lib/utils.ts, src/lib/hooks/useRentalSunsetNotice.ts, src/lib/console-url.ts — process.env.APIFY_CLIENT_BASE_URL / APIFY_CONSOLE_URL reads are replaced by resolveApiBaseUrl() / resolveConsoleUrl() from src/lib/runtime/target.ts. The env var still wins; the local runtime is used only while apify runtime connect is in effect; otherwise the result is undefined exactly as before. This is the one change that touches every API call the CLI makes, so it's the one to look at hardest.
  • src/commands/auth/login.ts — the hard-coded http://localhost:3333 used when Console is on localhost is replaced by resolveLoginApiBaseUrl(): APIFY_CLIENT_BASE_URL or a connected runtime first, then the runtime's last-used API port (default still 3333). Minor change for local platform development: APIFY_CLIENT_BASE_URL now takes precedence over the localhost fallback, where previously it was ignored in that case.
  • src/commands/actors/push.ts
    • Honors dockerContextDir from .actor/actor.json (monorepo Actors). Against the Apify platform (*.apify.com) a monorepo Actor is rejected up front with a clear error and exit code NotImplemented instead of pushing a broken partial context; against anything else it pushes the whole Docker context through a runtime-only endpoint. Actors without dockerContextDir go through the unchanged path.
    • After every push to a non-apify.com API host, registers the pushed folder as the Actor's live dev folder via POST /actor-runtime/dev-folder/:actorId. A 404 (not a runtime, e.g. a local platform dev stack) is silently ignored; other failures print a warning. Against *.apify.com no request is made.
    • Command description mentions dockerContextDir.
  • src/commands/actors/call.ts — new --dev-folder / --no-dev-folder flag (default on). It only does anything against a non-apify.com host; on the platform --no-dev-folder prints a warning and is ignored. The flag is visible in apify call --help and the docs for everyone.
  • src/lib/commands/run-on-cloud.ts — new optional extraStartParams; when set, the run is started through a raw httpClient.call instead of actor.start() (because apify-client rejects unknown options). Only call --no-dev-folder sets it. Also, the record-not-found error now appends the API's own message instead of a bare not found!, which changes the error text users see on the platform too.
  • src/lib/utils.ts — outputJobLog no longer crashes with a non-null assertion when a finished job has no log (if (log) guard). A small bug fix that applies on the platform as well.
  • src/lib/consts.ts — new ACTOR_RUNTIME_CONFIG_FILE_PATH constant.
  • src/commands/_register.ts, scripts/generate-cli-docs.ts — register the runtime command group and its docs section.

Release process and CI

  • .github/workflows/publish_to_npm.yaml — adds a runtime dist-tag option and an allow_unmerged_latest input. Publishing latest now refuses any ref not contained in origin/master unless that input is set (the checkout uses fetch-depth: 0 for that check). Adds a step summary naming what was published.
  • .github/scripts/before-beta-release.ts → before-prerelease.ts — generalized to take --tag and --bump-base-if-published, so side channels can publish from branches whose package.json still carries the last released version. pre_release.yaml updated to call it with --tag beta.
  • CONTRIBUTING.md — documents the side-channel publishing flow.

Shipped skill and existing tests

  • skills/apify/SKILL.md — new "Local Actor runtime" section for agents (Docker/Podman setup, npm i apify-cli@runtime, env vars). It ships inside the CLI package, so it reaches every user.
  • test/local/lib/command-framework/help.test.ts — registers RuntimeIndexCommand in the help-rendering test and asserts on its help text.
  • test/local/commands/login-api-base-url.test.ts, test/local/lib/run-on-cloud.test.ts — new tests for the shared-code changes above.

Follow-ups

  • skills/apify/SKILL.md and CONTRIBUTING.md say the runtime ships on the runtime dist-tag, not latest. Once this merges and ships, that text and the npm i apify-cli@runtime instructions should be updated.
  • Monorepo (dockerContextDir) push to the Apify platform is tracked in apify/apify-core#28685; until then it is runtime-only.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TUeZgjM9BnUMyjNmHXenHc


Generated by Claude Code

Pijukatel and others added 18 commits September 7, 2026 14:58
Co-authored-by: Claude <noreply@anthropic.com>
Points the runtime image at the development repository on Docker Hub, so
'apify runtime install' downloads it instead of expecting a locally built
placeholder. Reworks the pull-failure hint accordingly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpiku6d313JRBVoTVKNCXv
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Update the CLI to accept both Docker or Podman.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Actors running through local actor runtime will by default work in local
dev folder mode.

Depends on: apify/actor-runtime#43

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
`apify runtime install [image]` pulls the given image instead of the
default `apify/actor-runtime:latest`, so a pinned build or another
runtime implementation can be used:

```sh
apify runtime install apify/actor-runtime:master-5462005
```

- The installed image is recorded in
`~/.apify/actor-runtime/config.json`.
- `apify runtime start` runs whatever was installed last; no flags or
checks there.
- Without an argument, `install` keeps using the default image.
- No new dependencies.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01U7Bi6BtaXTmLFxAiV3gRZ3

Co-authored-by: Claude <noreply@anthropic.com>
'apify runtime status' reports whether the runtime container is running,
on which engine and image, the ports it publishes, the host directory
mounted as its data directory, and which API the CLI currently targets.
It exits 1 when the runtime is down, and takes --json.

'apify runtime connect' makes every CLI command target the local
runtime, and 'apify runtime disconnect' reverts to the Apify platform.
The connection is stored next to the installed image in
~/.apify/actor-runtime/config.json, so it holds across terminals and
does not touch the login. APIFY_CLIENT_BASE_URL and APIFY_CONSOLE_URL
keep taking precedence wherever they are set, so the current per-shell
behavior is unchanged; both commands say so when they are set.


Claude-Session: https://claude.ai/code/session_01PaySaAvWxMBNuVMSXXLqno

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds `apify runtime skill [--install]`, which reads the runtime's own
Agent Skill over HTTP when the runtime is running, or straight out of
the installed image when it is not — so `apify runtime install` is the
only prerequisite.

No copy is bundled with the CLI: one could only ever describe the image
the CLI was released against. `runtime install`, `start`, `connect` and
`status` now print one line pointing at the command.

Refs apify/actor-runtime#53. Needs apify/actor-runtime#54 to land first,
since the image must carry the file.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01ExpRUXDzyTmT828ugqkjAP

---------

Co-authored-by: Claude <noreply@anthropic.com>
…t updated (#1448)

`apify runtime install` no longer skips the pull when the image exists
locally, so an outdated `apify/actor-runtime:latest` gets updated from
the registry.
Only `@sha256:` digests short-circuit; `apify runtime start` still uses
the local copy.

---------

Co-authored-by: Claude <noreply@anthropic.com>
…time (#1463)

`apify push` no longer prints the dev-folder success notice; the runtime
now logs it in the build log (needs apify/actor-runtime counterpart).

Co-authored-by: Claude <noreply@anthropic.com>
…art' (#1465)

- `apify runtime start --api-port/--console-port` runs the Actor runtime
on other ports; they are remembered for later starts, `connect`,
`status`, `skill` and console links in `apify call`.
- Ports are ignored while disconnected, so platform traffic is
unaffected. Default ports keep older runtime images working.
- Needs apify/actor-runtime#80 for non-default
ports. No new dependencies.

---------

Co-authored-by: Claude <noreply@anthropic.com>
- `apify push` of an Actor whose `.actor/actor.json` sets
`dockerContextDir` sends its Git repository, as the platform's clone
would have it, to the local Actor runtime (apify/actor-runtime#89), with
the Actor's path in it.
- Files: tracked ones as on disk plus untracked ones Git doesn't ignore,
symlinks kept, no `.actorignore`; uncommitted changes are pushed with a
warning.
- A `dockerContextDir` outside the Git repository is rejected; the
Docker context is registered as the dev folder.
- Against the Apify platform, such a push stops with an error linking
apify/apify-core#28685.
- No new dependencies (uses the existing `archiver`).

---------

Co-authored-by: Claude <noreply@anthropic.com>
… Podman 3.x (#1485)

- `apify runtime start` adds `--network
slirp4netns:allow_host_loopback=true` on rootless Podman 3.x, read from
one `podman info` call; every other engine keeps the plain command.
- Why: the runtime reaches the web servers of the runs it starts (their
`containerUrl`, apify/actor-runtime#91) through that route; without it
only standby runs' servers are reachable there.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Resolves conflicts with the auth rework on master (profiles, getCurrentUserInfo,
client options moved to auth.ts):

- route the API base URL through resolveApiBaseUrl() in auth.ts, outputJobLog
  and the rental sunset notice, so `apify runtime connect` still applies
- call/push use getCurrentUserInfo(); push keeps the monorepo docker context
- runtime test mocks follow the getCurrentUserInfo rename
- regenerate docs/ for the merged flag sets

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015JzfLYa5CHCXxfzC1No3gV
…s 3333 (#1490)

## What

- `apify login` with a local Console URL always validated the token
against `http://localhost:3333`, so a runtime started on another port
(`apify runtime start --api-port 4333`, or `ACTOR_RUNTIME_API_PORT` from
apify/actor-runtime#80) was unreachable from login.
- Login now picks the API in this order: `APIFY_CLIENT_BASE_URL` or the
connected runtime, then the port the runtime was last started on, then
3333 when no port is known (hand-started runtime on the defaults, local
platform development).
- Adds `resolveLoginApiBaseUrl()` with unit tests for each of those
cases.

## Verified

- Against a real `apify/actor-runtime:latest` started with `--api-port
4333 --console-port 4000`: login succeeds while connected, and with only
`APIFY_CONSOLE_URL=http://localhost:4000` set, with nothing listening on
3333.
- Lint, format, build, and `test:local` (the only failures are the
pre-existing root-user "cannot write" cases).

Co-authored-by: Claude <noreply@anthropic.com>
## What

The CI probe [#1493](#1493)
showed `Local Tests (windows-2025)` red on `actor-runtime` because two
branch-only tests assume POSIX paths and modes. The CLI code is fine on
Windows; only the assertions were wrong.

- `test/local/lib/runtime-source-context.test.ts`: expected roots were
built with `join('/repo')`, which is `\repo` on Windows, while
`resolveDockerContext` uses `resolve()` and gets `D:\repo`. The test now
resolves the same way.
- `test/local/commands/push-monorepo.test.ts`: the tarball mode
assertion expected `0o755` after `chmod`, but Windows has no execute
bit, so the archive records `0o666`. That one assertion is skipped on
Windows, as the existing mode tests in `auth-file` and `credentials`
already do; the rest of the test still runs there.

Test-only change; no CLI behavior changes.

Co-authored-by: Claude <noreply@anthropic.com>
…esktops (#1495)

Two cases where `apify runtime start` produced a runtime that reported
the engine as unavailable. Both surfaced on actor-runtime's macOS CI leg
([apify/actor-runtime#65](apify/actor-runtime#65)).

**SELinux on Podman hosts**
- Under SELinux enforcing (Fedora, RHEL, and the Fedora-based VM of
`podman machine`, so Podman Desktop on macOS and Windows), the runtime
container may not connect to the engine's API socket bind-mounted into
it: the socket carries the engine's label, not a container's.
- `podmanInfo` now also reads `Host.Security.SELinuxEnabled`;
`buildRuntimeRunArgs` adds `--security-opt label=disable` via the new
`runtimeSecurityOptions`, next to `runtimeNetworkMode`.
- Only the runtime container runs unconfined. The Actor containers it
starts stay confined (verified by actor-runtime's dev-folder e2e under
enforcing SELinux).

**Docker socket on macOS and Windows**
- The daemon lives in a VM there and serves `/var/run/docker.sock`
inside it on every desktop engine. A `DOCKER_HOST` unix socket is a host
path (Colima's docs suggest exporting one for tools that ignore
contexts), which a bind mount cannot carry into the VM.
- `resolveEngineSocketPath` now consults `DOCKER_HOST` only on Linux.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@Pijukatel Pijukatel changed the title feat: local Actor runtime (apify runtime) feat: Add local Actor runtime (apify runtime) Oct 13, 2026
Marks the Actor runtime as experimental in the CLI, with as little text
as possible.

---------

Co-authored-by: Claude <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants