Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ COPY --from=browser-viewer-payload /payload/version.txt /opt/apify-browser-viewe
VOLUME ["/data"]
ENV ACTOR_RUNTIME_DATA_DIR=/data

# Defaults; ACTOR_RUNTIME_API_PORT / ACTOR_RUNTIME_CONSOLE_PORT move them.
EXPOSE 3333 3000

CMD ["node", "dist/index.js"]
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ The console is at [http://localhost:3000](http://localhost:3000). The full walkt
Skill: `apify runtime skill` prints it, and `apify runtime skill --install` installs it for your coding
agent.

Port 3333 or 3000 already taken? `apify runtime start --api-port 4333 --console-port 4000` moves them.

If you are not logged in, any non-empty token works (`apify login --token local-dev-token`).

In build and run logs, everything the runtime itself says opens with a blue `[actor-runtime]` prefix.
Expand Down Expand Up @@ -188,8 +190,10 @@ podman run --rm -p 3333:3333 -p 3000:3000 \
- For rootful Podman, mount `/run/podman/podman.sock` and run with `sudo`. For rootless Docker, mount
`$XDG_RUNTIME_DIR/docker.sock`. To mount the socket at another path, also set
`-e DOCKER_HOST=unix:///that/path`.
- Podman 3.4 (Ubuntu 22.04's stock package) and newer are supported. Keep `-p 3333:3333` published on
all interfaces: under Podman 3.x and rootless Podman, Actors reach the API through it.
- To use other ports, set `-e ACTOR_RUNTIME_API_PORT=4333 -e ACTOR_RUNTIME_CONSOLE_PORT=4000` and publish
the same numbers on the host (`-p 4333:4333 -p 4000:4000`).
- Podman 3.4 (Ubuntu 22.04's stock package) and newer are supported. Keep the API port published on
all interfaces (`-p 3333:3333` by default): under Podman 3.x and rootless Podman, Actors reach the API through it.
- Podman does not create a missing bind-mount directory, hence `mkdir -p data`. `apify runtime start`
creates its data directory itself.
- Short image names in an Actor's `FROM` line (`apify/actor-node:20`) resolve to Docker Hub, as on the
Expand Down
2 changes: 1 addition & 1 deletion requirements/actor-driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ start`, ...) is refused by name, naming both the `CMD` fix and how to clear debu

# Networking

- Every Actor container reaches the runtime's API at `http://apify-api:3333`, whatever the host's own
- Every Actor container reaches the runtime's API at `http://apify-api:<API port>`, whatever the host's own
networking, whichever supported engine runs the containers, and however the runtime itself was started
(as a container or not). The runtime provides the `apify-local` network with the DNS alias `apify-api`
for this; when it has to reach the same goal another way, it says so at startup.
Expand Down
2 changes: 1 addition & 1 deletion requirements/console.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Frontend

- Console frontend is a page that allows inspecting each user's objects across the whole runtime.
- Server-rendered HTML on its own fixed port (3000); the console reflects the same live state the API serves.
- Server-rendered HTML on its own port (`system.md`); the console reflects the same live state the API serves.
- Frontend shows for each object the owner (`userId`).
- The console has no login of its own, so with multiple users it lists and shows every user's objects
rather than scoping to one - the API's own endpoints stay strictly scoped to the calling token's user
Expand Down
12 changes: 7 additions & 5 deletions requirements/system.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,26 @@
- The system is isolated environment that is started by running the docker container.
- The system user interface is accessible on localhost with specific ports for console frontend and API.
- The user interacts with the system through the Apify cli.
- On startup the container prints a banner naming the API port (3333) and the console port (3000),
- On startup the container prints a banner naming the API port and the console port,
plus a warning if the host's Docker socket could not be reached - builds and runs then fail fast
with a clear status message, while every other endpoint (storages, actor/build/run records,
console) still works.
- Both ports are fixed and not configurable.
- Port 3333 also serves the per-run events websocket and standby Actors (`api.md`); no additional port is
- The API port (default 3333) and console port (default 3000) are set by `ACTOR_RUNTIME_API_PORT` and
`ACTOR_RUNTIME_CONSOLE_PORT`, published under the same numbers on the host. Every URL the runtime shows
uses them.
- The API port also serves the per-run events websocket and standby Actors (`api.md`); no additional port is
published for either.
- **Debug mode is the one exception to "no other Actor container port is ever published"**
(`actor-driver.md`'s "Debug mode" section): when debug mode is on for an Actor, that Actor's runs get a
port published on the host, bound to `127.0.0.1` (`5678` Python / `9229` Node by default, per-Actor
overridable) - the runtime's own two ports above are unaffected, and no port is published for an Actor
that never turned debug mode on.
- Browser view (`actor-driver.md`) publishes no port on the host; the view is served on the console's port 3000.
- Browser view (`actor-driver.md`) publishes no port on the host; the view is served on the console's port.
- Required `docker run` flags: mount the host's Docker-Engine-API socket read-write
(`-v /var/run/docker.sock:/var/run/docker.sock`) so the runtime can build and run Actor containers,
and mount a persistent data directory (`-v <host-dir>:/data`, e.g. `-v "$(pwd)/data:/data"`) so
storages survive a restart and are easy to inspect from the host; the directory must exist before the
runtime starts. Publish both fixed ports
runtime starts. Publish both ports
(`-p 3333:3333 -p 3000:3000`). The canonical start command is:

```bash
Expand Down
10 changes: 9 additions & 1 deletion skills/actor-runtime/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Drive the local Apify Actor runtime - a self-contained local Apify

These are the operating instructions for the Actor runtime the reader is talking to: a local Apify
platform serving an Apify-compatible API on `http://localhost:3333` and a console UI on
`http://localhost:3000`. It emulates the subset of the Apify API needed to develop, run and debug
`http://localhost:3000` (the defaults - see "Other ports" below). It emulates the subset of the Apify API needed to develop, run and debug
Actors locally, so no change needs a rebuild on the real platform to be tried out.

It is not the Apify platform. Actors, builds, runs and storages created here exist only in this
Expand Down Expand Up @@ -35,6 +35,14 @@ Any non-empty token authenticates - `apify login --token local-dev-token` is eno
`APIFY_DISABLE_KEYRING=1` first in a sandbox with no OS keyring. To act as a second user, pass a
different token on a single call: `apify api v2/datasets -H '{"authorization": "Bearer OTHER"}'`.

### Other ports

When 3333 or 3000 is taken on this machine, start the runtime on other ports:
`apify runtime start --api-port 4333 --console-port 4000`. The CLI remembers them for later starts and
for `apify runtime connect`; with a hand-started container, set `-e ACTOR_RUNTIME_API_PORT=4333
-e ACTOR_RUNTIME_CONSOLE_PORT=4000` and publish the same numbers (`-p 4333:4333 -p 4000:4000`). Every URL
in this document then uses those ports instead, `apify-api:3333` included.

## The normal loop

```sh
Expand Down
28 changes: 23 additions & 5 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
/** Fixed, non-configurable ports (`system.md`): identical on every start, no env var overrides them. */
export const API_PORT = 3333;
export const CONSOLE_PORT = 3000;
export const DEFAULT_API_PORT = 3333;
export const DEFAULT_CONSOLE_PORT = 3000;

/** A port from the runtime's own environment (`system.md`). It is the port both listened on inside the
* container and published on the host (`-p N:N`): the URLs the runtime hands out, and the host-gateway
* route Actor containers may take to the API, assume the two are the same. */
export function portFromEnv(name: string, fallback: number, env: NodeJS.ProcessEnv = process.env): number {
const raw = env[name]?.trim();
if (!raw) return fallback;
const port = Number(raw);
if (!/^\d+$/.test(raw) || port < 1 || port > 65535) {
throw new Error(`${name} must be a TCP port number between 1 and 65535, got '${raw}'.`);
}
return port;
}

export const API_PORT = portFromEnv('ACTOR_RUNTIME_API_PORT', DEFAULT_API_PORT);
export const CONSOLE_PORT = portFromEnv('ACTOR_RUNTIME_CONSOLE_PORT', DEFAULT_CONSOLE_PORT);
if (API_PORT === CONSOLE_PORT) {
throw new Error(`ACTOR_RUNTIME_API_PORT and ACTOR_RUNTIME_CONSOLE_PORT must differ, both are ${API_PORT}.`);
}

/** The DNS alias every Actor container resolves the runtime's own API by, on the `apify-local` network. */
export const CONTAINER_API_ALIAS = 'apify-api';
Expand All @@ -9,10 +27,10 @@ export const CONTAINER_API_BASE_URL = `http://${CONTAINER_API_ALIAS}:${API_PORT}
/** Base for the events-websocket URL every Actor container is given (`ACTOR_EVENTS_WEBSOCKET_URL` /
* `APIFY_ACTOR_EVENTS_WS_URL`, `services/runs.ts: buildEnv`) - the same host:port as
* `CONTAINER_API_BASE_URL`, just `ws://` instead of `http://`: the events endpoint upgrades on the
* existing API server (`api/events-ws.ts`), not a second port (`system.md`'s fixed-ports contract). */
* existing API server (`api/events-ws.ts`), not a second port (`system.md`). */
export const CONTAINER_EVENTS_WS_BASE_URL = `ws://${CONTAINER_API_ALIAS}:${API_PORT}`;

/** Host-facing base URL for the local console UI (fixed port, `system.md`) - used only to build the
/** Host-facing base URL for the local console UI (`system.md`) - used only to build the
* `consoleUrl` field storage DTOs return (the real platform's equivalent points at
* `console.apify.com`; this points at the one console this runtime actually serves). The path appended
* after this base uses the real platform's URL shape (e.g. `/storage/datasets/:id`), which the console
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async function main(): Promise<void> {

const apiServer = apiApp.listen(API_PORT);
const consoleServer = consoleApp.listen(CONSOLE_PORT);
// Upgrades on the same API server/port - no second port (`system.md`'s fixed-ports contract); see
// Upgrades on the same API server/port - no second port (`system.md`); see
// `api/events-ws.ts`'s own doc comment for why this attaches here rather than inside `createApiServer`
// (Express never sees an `upgrade` event, so this needs the actual `http.Server` `listen()` returned).
const eventsWebSocketServer = attachEventsWebSocket(apiServer, (req, socket, head) =>
Expand Down
20 changes: 20 additions & 0 deletions test/unit/config-ports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest';

import { portFromEnv } from '../../src/config.js';

describe('portFromEnv', () => {
it('falls back to the default when the variable is unset or blank', () => {
expect(portFromEnv('ACTOR_RUNTIME_API_PORT', 3333, {})).toBe(3333);
expect(portFromEnv('ACTOR_RUNTIME_API_PORT', 3333, { ACTOR_RUNTIME_API_PORT: ' ' })).toBe(3333);
});

it('reads a valid port', () => {
expect(portFromEnv('ACTOR_RUNTIME_API_PORT', 3333, { ACTOR_RUNTIME_API_PORT: ' 4333 ' })).toBe(4333);
});

it.each(['0', '65536', 'abc', '80.5', '-1', '3e3'])('rejects %s', (value) => {
expect(() => portFromEnv('ACTOR_RUNTIME_API_PORT', 3333, { ACTOR_RUNTIME_API_PORT: value })).toThrow(
/ACTOR_RUNTIME_API_PORT must be a TCP port/,
);
});
});
Loading