Skip to main content

Debug Actors on the Apify platform

You can reproduce most bugs by running your Actor locally and using your IDE's debugger. However, some bugs are related to the platform's proxies, memory limits, environment variables, or data that exists only in a real run. This guide shows how to attach a debugger to such a run on the Apify platform.

Infrastructure constraints

An Actor run is a Docker container on a shared worker machine. You can't open a TCP connection to the container, so there's no SSH and no port forwarding.

The one inbound channel is the container web server. Whatever listens on ACTOR_WEB_SERVER_PORT (default 4321) inside the container is reachable at the run's container URL, https://<run>.runs.apify.net. The platform forwards HTTP and WebSocket traffic to that port. It doesn't forward raw TCP.

Debuggers speak raw TCP. The Node.js inspector listens on port 9229, debugpy on 5678. Bridging that mismatch takes a small server inside the container, which carries the debug port over WebSocket on the web server port.

These constraints shape what debugging on the platform looks like:

  • Without an access check, anyone who can reach the container URL can reach the debugger. A debugger is a code-execution channel into the run, with access to its environment, including APIFY_TOKEN.
  • A run paused on a breakpoint keeps consuming compute and counts toward the run timeout. Set a generous timeout and abort the run when you finish.
  • A migration restarts the container and drops the debug session.
  • Breakpoints bind to the deployed code. Keep your local checkout at the same commit as the build you debug.

Attach a debugger

You have two independent options:

Actor debuggerwstunnel
Where you debugIn any browser, with no local toolingIn your local IDE
What you add to the imageThe actor-debugger packageThe wstunnel binary
LanguagesNode.js/TypeScript, PythonAnything with a TCP debug protocol
Best forA quick look at a runA full IDE experience

Actor debugger

The experimental actor-debugger package launches your Actor under its native debugger and serves a debugger UI on the web server port. You open one URL from the run log in your browser. Nothing runs on your machine.

  1. Install the package in your Dockerfile, after your other dependencies, so the project's npm install doesn't prune it:

    RUN npm install actor-debugger
  2. Replace the Dockerfile entrypoint, for example CMD ["node", "dist/main.js"], with the debug launcher:

    CMD ["npx", "actor-debugger", "--brk"]

    The launcher finds your entrypoint from scripts.start or main in package.json, or from conventional paths like dist/main.js. To name the entrypoint yourself, pass its path: CMD ["npx", "actor-debugger", "dist/main.js"].

  3. Build the Actor, start a run, and open the URL from the run log in your browser:

    https://<run>.runs.apify.net/devtools/js_app.html?wss=<run>.runs.apify.net/<uuid>

    That page is Chrome DevTools connected to your Actor. TypeScript sources show up through source maps. The launcher inlines external .js.map files at startup, so any tsc build with sourceMap or inlineSourceMap enabled works.

The --brk flag pauses the Actor on its first line and holds the run there until you open the debugger URL. Without the flag, the Actor starts working immediately and you can open the URL at any point during the run. To stop debugging, restore the original CMD and rebuild the Actor.

wstunnel

wstunnel tunnels TCP over WebSocket. The server runs inside the container on the web server port. The client runs on your machine and exposes the remote debug port on localhost. Your IDE attaches to localhost as if the Actor ran there. The tunnel works for any language with a TCP debug protocol.

  1. Download the static wstunnel release binary in your Dockerfile:

    FROM apify/actor-node:24

    ARG WSTUNNEL_VERSION=10.7.1
    RUN wget -qO- "https://github.com/erebe/wstunnel/releases/download/v${WSTUNNEL_VERSION}/wstunnel_${WSTUNNEL_VERSION}_linux_amd64.tar.gz" \
    | tar -xz -C /usr/local/bin wstunnel \
    && chmod +x /usr/local/bin/wstunnel
  2. Define a DEBUG_SECRET environment variable in the Actor version and mark it as secret. wstunnel accepts only WebSocket upgrades whose path starts with this value, which keeps random visitors of the container URL out.

  3. Replace the CMD in your Dockerfile, so the container starts the tunnel server and the Actor under its debugger:

    CMD ["sh", "-c", ": \"${DEBUG_SECRET:?}\"; wstunnel server --restrict-to 127.0.0.1:9229 --restrict-http-upgrade-path-prefix \"$DEBUG_SECRET\" \"ws://0.0.0.0:$ACTOR_WEB_SERVER_PORT\" & exec node --inspect-brk=127.0.0.1:9229 dist/main.js"]

    Replace dist/main.js with your Actor's entrypoint. The --inspect-brk flag pauses the Actor on its first line until a debugger attaches. Use --inspect instead to let the Actor start working immediately.

    The : "${DEBUG_SECRET:?}" guard fails the run before anything starts when the variable is unset or empty, because an empty prefix lets in any client. The --restrict-to option limits the tunnel to the debug port, so nothing else in the container becomes reachable. The exec command keeps the Actor as the main process, so it still receives the platform's shutdown signals.

  4. Build the Actor, start a run, and copy the container URL from the run detail page in Apify Console.

  5. Install wstunnel on your machine with brew install wstunnel or download a release binary. Then open the tunnel, and leave the command running:

    wstunnel client --http-upgrade-path-prefix <DEBUG_SECRET> -L tcp://9229:127.0.0.1:9229 wss://<run>.runs.apify.net

    Use 5678 in place of both occurrences of 9229 for Python. The port on localhost now leads to the debugger inside the run.

  6. Attach your IDE to localhost and map your project root to /usr/src/app, the working directory in the Apify base images:

    {
    "type": "node",
    "request": "attach",
    "name": "Attach to Apify run",
    "address": "localhost",
    "port": 9229,
    "localRoot": "${workspaceFolder}",
    "remoteRoot": "/usr/src/app"
    }

    In JetBrains IDEs, create an Attach to Node.js/Chrome run configuration for localhost:9229 instead, and map the project root to /usr/src/app under Remote URLs of local files. In Chrome, open chrome://inspect and add localhost:9229 as a target.

  7. Set a breakpoint in your source files and start the configuration. The run resumes under your debugger.

You can also start wstunnel server from your Actor code and gate it on an input field. That approach avoids a separate debug build at the cost of shipping the binary in every build.

Keep debugging out of production

Both options turn the run's container URL into a code-execution endpoint. The Actor debugger has no authentication, and with wstunnel, anyone who learns the secret gets the same access.

Never publish a debug build

A build with a debug entrypoint gives its users access to your Actor's environment, including APIFY_TOKEN. Keep such builds out of Apify Store.

Limit the exposure while you debug:

  • Keep the debug CMD in a dedicated Actor version with its own build tag. Production builds keep their normal entrypoint.
  • Run debug builds with limited permissions where the Actor allows it.
  • Abort the run when you finish.