Sign inSign up

eworkerinc/remote-runner

By eworkerinc

Updated 4 months ago

Self-hosted E-Worker command runner with files, artifacts, previews, and terminals.

Image
0

10K+

eworkerinc/remote-runner repository overview

E-Worker Remote Runner

Remote Runner is a Docker image for E-Worker. You run it as a container, then approved E-Worker AI tools can use that container as a separate Linux place for command-line work.

Common uses:

  • Copy a project into the container.

  • Run scripts, tests, builds, and code checks.

  • Install project tools into mounted folders.

  • Collect logs, generated files, and ZIP artifacts.

  • Keep that work away from the main E-Worker app process.

  • E-Worker connects to the runner over HTTP or HTTPS.

  • Approved agent tools can run commands, sync files, collect logs, and return artifacts.

  • A secret API key is required by default; treat it like a password.

  • Commands run as the container user and can modify the folders you mount into the container.

  • File, artifact, and workspace-sync APIs are scoped to the configured workspace root and reject paths that pass through workspace symlinks.

  • Task commands get a scrubbed environment by default, so runner service variables such as API keys are not inherited by command processes.

  • This is useful Docker container separation, but it is not a complete security sandbox by itself.

  • The image includes sudo and an optional remote-runner user (10001:10001) for stricter deployments that run tasks as a non-root user.

Docker image

  • Runner service image: eworkerinc/remote-runner:0.6.3
  • Reusable tools image: eworkerinc/remote-runner-env:2026.06.02

Use the runner service image in Docker Compose and deployment commands. The runner service image is intentionally small: it contains the Remote Runner API service and container entrypoint. It is built on top of the reusable tools image.

The reusable tools image is the large layer. It contains the SDKs, browsers, fonts, browser helper scripts, and build utilities that agents commonly need. It changes when those installed tools change or when the image is rebuilt for OS security updates. This keeps normal runner service updates much smaller after the first pull.

Versioning policy:

  • Use the current image repository: eworkerinc/remote-runner.
  • Pin production deployments to a version tag such as 0.6.3.
  • latest tracks the current stable tag.
  • The remote-runner-env tag is pinned separately because tool updates and service updates do not always happen together.

What it gives you

  • A private runner container for approved E-Worker command tasks.
  • Workspace sync so an agent can copy code into the runner and bring results back.
  • Logs and artifacts so AI work can return command output, generated files, or ZIP packages.
  • One reusable image that can be started as many named containers for different projects or teams.

Chat, assistant, and agent workflows can use the runner only when Remote Runner tools are enabled and allowed for that workflow. Agent Console can use it when the selected agent has Remote Runner access in its policy.

Building the images

From the repository:

cd E-Worker-Services/Remote-Runner

# Normal runner code change: rebuild only the small service image.
Scripts/Build-Docker-Images.sh --runner-only

# Toolchain change: rebuild the large tools image, then the service image.
Scripts/Build-Docker-Images.sh

# Security refresh: pull the latest base image, disable Docker cache, upgrade OS packages, then rebuild both images.
# Use a new dated environment tag when publishing.
Scripts/Build-Docker-Images.sh --security-refresh \
  --env-image "eworkerinc/remote-runner-env:$(date +%Y.%m.%d)" \
  --runner-image eworkerinc/remote-runner:NEXT_VERSION

Use Scripts/Build-Docker-Images.sh --push when publishing the built tags. A public security refresh should normally use a new dated remote-runner-env tag and a new runner service version tag, then publish the runner service image on top of the refreshed environment image.

Recommended cadence: run a security refresh at least weekly, and sooner when a relevant base image, browser, runtime, or OS package vulnerability is announced.

How isolated is it?

The runner gives work a separate Docker container filesystem and process boundary. The default compose example lets tasks run as root inside the container, so approved agents can install missing OS packages and project tools.

That is useful isolation, but it is not the same as a hardened sandbox or a separate virtual machine. Commands run with the permissions of the container user. They can read and write the folders you mount, such as /workspace and /data.

For stronger isolation, use a dedicated VM or locked-down container host, private network, VPN or gateway, narrow volume mounts, firewall rules, and resource limits. Do not expose the runner directly to the public Internet.

Use separate runner containers for separate trust boundaries. One shared runner means shared mounted folders, task history, and command execution context.

Runner environment

The reusable tools image is a developer-capable generic environment. It includes the tools agents commonly need when they write helper code, inspect projects, run tests, or build artifacts:

  • .NET SDK
  • PowerShell (pwsh) inherited from the Microsoft .NET SDK base image; the exact version follows the base image used when remote-runner-env was rebuilt
  • Python 3 with pip, venv, pipx, and Python headers for common build tasks
  • Node.js with npm
  • Go
  • git, curl, wget, jq, ripgrep, fd, build-essential, pkg-config, make, patch, rsync, zip, unzip, sqlite3, SSH client, and common shell/network utilities
  • Xvfb, Playwright 1.60.0 for Node and Python, Playwright-managed Chromium/Firefox/WebKit browsers, a playwright-chromium executable wrapper, ffmpeg, ImageMagick, and common web fonts for browser testing and debugging

Users and agents can still install missing software inside the running runner container or download project-specific tools into mounted folders such as /workspace/.tools or /data/tools. Those folders are already on PATH, along with /data/tools/npm-global/bin and /data/tools/go/bin.

For highly repeatable work, build a custom image from this one and preinstall your team-specific CLIs, SDKs, or private package configuration. Installing OS packages inside a running container is supported by the default compose profile, but those changes live in the container filesystem and can be lost when the container is recreated.

The default image does not expose Docker or Podman inside the container.

Quick start with Docker Compose

This starts one runner container with persistent /data and /workspace folders. The example sets the API key to change-me; replace it with your own long secret.

services:
  remote-runner:
    image: eworkerinc/remote-runner:0.6.3
    restart: unless-stopped
    environment:
      EW_REMOTE_RUNNER_LISTEN_ADDR: 0.0.0.0:62480
      EW_REMOTE_RUNNER_DATA_DIR: /data
      EW_REMOTE_RUNNER_WORKSPACE_ROOT: /workspace
      EW_REMOTE_RUNNER_CORS_ORIGINS: https://app.eworker.ca
      EW_REMOTE_RUNNER_PREVIEW_BIND_ADDR: "127.0.0.1"
      EW_REMOTE_RUNNER_PREVIEW_PORTS: "13000-13099"
      EW_REMOTE_RUNNER_PREVIEW_HOST_PORTS: "13000-13099"
      EW_REMOTE_RUNNER_API_KEY: change-me
      EW_REMOTE_RUNNER_API_KEY_FILE: /data/remote-runner-api-key
      EW_REMOTE_RUNNER_MAX_CONCURRENT: "10"
      EW_REMOTE_RUNNER_MAX_TERMINAL_SESSIONS: "20"
      EW_REMOTE_RUNNER_DEFAULT_TIMEOUT_MS: "600000"
      EW_REMOTE_RUNNER_SYNC_WAIT_TIMEOUT_MS: "600000"
      EW_REMOTE_RUNNER_RUN_AS_USER: "false"
    ports:
      # Runner API, local-only default. Use 0.0.0.0 only behind private network, VPN, or gateway.
      - "127.0.0.1:62480:62480"
      # Local-only development preview ports for servers started inside the runner.
      - "127.0.0.1:13000-13099:13000-13099"
    volumes:
      - ./Data:/data
      - ./Workspace:/workspace
    shm_size: 1g
    pids_limit: 1024
    mem_limit: 4g
    cpus: 4

Start it:

docker compose up -d

If you remove EW_REMOTE_RUNNER_API_KEY, the entrypoint generates a key for you. Read it from the data folder or container logs:

cat ./Data/remote-runner-api-key
docker logs <container-name>

The default compose example keeps software installation simple: tasks run root-capable inside the container, and sudo is installed for agents that use common Linux setup commands.

The compose example includes browser-friendly CPU, memory, process, and /dev/shm limits. Raise them for larger builds or lower them for smaller shared hosts.

For narrow prebuilt workloads that should not install OS packages, add the optional hardened override:

docker compose -f docker-compose.yml -f docker-compose.hardened.yml up -d

The hardened override starts the container as UID/GID 10001, drops Linux capabilities, and enables no-new-privileges. Use it only when the runner already has everything it needs. Make sure mounted folders are writable by UID/GID 10001.

Development preview ports

The default compose example publishes one local-only preview range:

  • 127.0.0.1:13000-13099 on the Docker host
  • 13000-13099 inside the runner container

Agents use this range for long-running dev servers while builds, installs, and tests stay inside the isolated runner. The runner also reports the configured mapping in /v1/info as previewPorts, so E-Worker tools can discover custom ranges instead of guessing.

Start dev servers inside the container on 0.0.0.0, then open the matching host URL:

# Vite
npm run dev -- --host 0.0.0.0 --port 13000

# Next.js
npm run dev -- --hostname 0.0.0.0 --port 13000

# Python static server
python -m http.server 13000 --bind 0.0.0.0

Open the matching host URL, for example http://127.0.0.1:13000 for container port 13000, or http://127.0.0.1:13001 for container port 13001.

Keep preview ports local-only unless you intentionally expose them behind a private network, VPN, or gateway. If you change the Docker port mapping, update EW_REMOTE_RUNNER_PREVIEW_PORTS and EW_REMOTE_RUNNER_PREVIEW_HOST_PORTS so /v1/info remains accurate.

Docker binds the full host preview range when the container starts. If a host port is already allocated, keep EW_REMOTE_RUNNER_PREVIEW_PORTS=13000-13099 for container ports and move only the host range, for example EW_REMOTE_RUNNER_PREVIEW_HOST_PORTS=13100-13199. This lets agents keep using the same internal ports while each runner gets its own host range.

To disable preview ports, remove the preview ports line from docker-compose.yml and set EW_REMOTE_RUNNER_PREVIEW_PORTS=none.

For multiple named runners, give each runner a unique host preview range. Leave the container preview range at the default 13000-13099 unless you intentionally want agents to use different internal ports:

Scripts/Create-Docker-Runner.sh \
  --workspace-id acme-workspace \
  --runner-name debug-linux \
  --host-port 62501 \
  --preview-host-ports 13100-13199

Shell access

The runner does not start an SSH server. To open a shell, run Docker commands on the Docker host. From another machine, SSH to the Docker host first, then use Docker exec.

From the compose folder:

docker compose exec -w /workspace remote-runner bash
docker compose exec -w /workspace remote-runner sh

The published runner image includes PowerShell (pwsh) from the Microsoft .NET SDK base image. Its exact version follows the mcr.microsoft.com/dotnet/sdk:10.0 image used when remote-runner-env was rebuilt.

docker compose exec -w /workspace remote-runner pwsh

Using the default container name:

docker exec -it -w /workspace ew-remote-runner bash
docker exec -it -w /workspace ew-remote-runner sh
docker exec -it -w /workspace ew-remote-runner pwsh

Find the actual container name:

docker compose ps
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"

E-Worker can also open browser-based terminal sessions for saved Remote Runner records. Use AI Ecosystem -> Remote Runners -> Open Terminal, or open a terminal from the workspace Remote Runners tree/folder viewer. These sessions use short-lived WebSocket tokens and are capped by EW_REMOTE_RUNNER_MAX_TERMINAL_SESSIONS (default 20).

Browser debugging

Run a browser smoke test inside the container:

browser-debug-smoke

Start a Chromium session with Chrome DevTools Protocol enabled:

debug-chrome https://example.com

debug-chrome listens on container port 9222, uses headless mode automatically when no DISPLAY is available, and writes screenshots/traces under BROWSER_ARTIFACTS_DIR (/workspace/.artifacts/browser by default).

Do not expose Chrome DevTools/CDP to the public Internet. For local debugging, use the optional compose override from this repository:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.browser-debug.yml \
  up -d

Verify

export EW_REMOTE_RUNNER_API_KEY="change-me"

curl -sS http://127.0.0.1:62480/healthz

curl -sS \
  -H "Authorization: Bearer $EW_REMOTE_RUNNER_API_KEY" \
  http://127.0.0.1:62480/v1/info

curl -sS \
  -H "Authorization: Bearer $EW_REMOTE_RUNNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mode":"sync","shell":"sh","command":"python --version && node --version && go version && pwd"}' \
  http://127.0.0.1:62480/v1/tasks

Named runner containers

Use one image as a template for many named runner containers:

cd E-Worker-Services/Remote-Runner
chmod +x Scripts/Create-Docker-Runner.sh

Scripts/Create-Docker-Runner.sh \
  --workspace-id acme-workspace \
  --runner-name debug-linux \
  --host-port 62501 \
  --image-tag eworkerinc/remote-runner:0.6.3

The helper writes:

  • Instances/<workspace-id>/<runner-name>/.env
  • Instances/<workspace-id>/<runner-name>/api-key.txt
  • Instances/<workspace-id>/<runner-name>/Data/
  • Instances/<workspace-id>/<runner-name>/Workspace/

HTTPS and CORS

Remote Runner supports HTTPS. Use it when browser-based E-Worker is served over HTTPS and calls the runner directly.

Create a local test certificate:

mkdir -p certs

openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
  -keyout certs/service.key -out certs/service.crt \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

Then add these lines under the existing environment and volumes sections:

# under environment:
EW_REMOTE_RUNNER_TLS_CERT_FILE: /certs/service.crt
EW_REMOTE_RUNNER_TLS_KEY_FILE: /certs/service.key
EW_REMOTE_RUNNER_CORS_ORIGINS: https://app.eworker.ca

# under volumes:
- ./certs:/certs:ro

Only set the two TLS file variables after ./certs/service.crt and ./certs/service.key exist on the host and the folder is mounted as /certs. If you do not need HTTPS yet, remove both TLS file variables and use the HTTP URL.

Restart the container and verify it:

curl -k https://127.0.0.1:62480/healthz

Use -k only while testing a self-signed certificate. For LAN access, create a certificate that includes the real host name or IP address, then use that HTTPS URL in E-Worker.

CORS options

CORS allows E-Worker Studio by default. Set it to none for private desktop or server-to-server flows where the browser will not call the runner directly. The examples in this section use .env syntax:

EW_REMOTE_RUNNER_CORS_ORIGINS=https://app.eworker.ca

Disable browser CORS:

EW_REMOTE_RUNNER_CORS_ORIGINS=none

Allow E-Worker Studio plus a customer-managed browser origin with a comma-separated list:

EW_REMOTE_RUNNER_CORS_ORIGINS=https://app.eworker.ca,https://studio.example.com

Use * only for trusted local testing:

EW_REMOTE_RUNNER_CORS_ORIGINS=*

If you edit docker-compose.yml directly, use YAML mapping syntax under environment. Use :, not =, and quote * because it has special meaning in YAML:

environment:
  # allow E-Worker Studio
  EW_REMOTE_RUNNER_CORS_ORIGINS: "https://app.eworker.ca"

  # or allow multiple origins
  # EW_REMOTE_RUNNER_CORS_ORIGINS: "https://app.eworker.ca,https://studio.example.com"

  # or, for trusted local testing only
  # EW_REMOTE_RUNNER_CORS_ORIGINS: "*"

Origins must match the browser origin exactly. Include the scheme and port when needed. Do not add paths or a trailing slash.

If you use the repo docker-compose.yml, put the value in the .env file next to the compose file, then recreate the container:

docker compose up -d --force-recreate

For named runners, pass CORS when creating the runner:

Scripts/Create-Docker-Runner.sh \
  --workspace-id acme-workspace \
  --runner-name secure-linux \
  --host-port 62502 \
  --cors-origins https://app.eworker.ca

Check a browser preflight:

curl -k -i -X OPTIONS https://127.0.0.1:62480/v1/info \
  -H "Origin: https://app.eworker.ca" \
  -H "Access-Control-Request-Method: GET" \
  -H "Access-Control-Request-Headers: Authorization"

E-Worker setup

In E-Worker AI Ecosystem, add a Remote Runner record:

  • Base URL, for example http://127.0.0.1:62480, or https://127.0.0.1:62480 when HTTPS is enabled
  • API key from EW_REMOTE_RUNNER_API_KEY or Data/remote-runner-api-key

Agent Console can then use approved tools for:

  • runner info and profiles
  • command tasks, status, logs, streaming, and cancel
  • file upload, download, and listing
  • artifact upload, download, and workspace sync

Users can also open interactive terminal sessions from AI Ecosystem or the workspace Remote Runners browser. The browser requests a short-lived terminal token with the configured API key, then connects to the runner over WebSocket. Open more than one terminal when needed and choose an available shell such as bash, sh, or pwsh.

Safety notes

  • Do not expose the runner directly to the public Internet.
  • Treat the API key like a password.
  • Prefer one runner per project/workspace when you want clearer separation.
  • Mount only the folders the runner actually needs.
  • Keep symlinks out of mounted workspace paths; runner file APIs reject symlink traversal to avoid workspace escape.
  • Keep service data and workspace folders separate; do not nest one inside the other.
  • Tune CPU, memory, and process limits for each runner.
  • Put the runner behind a private network, VPN, gateway, or firewall for business deployments.

Tag summary

Content type

Image

Digest

sha256:da2506852

Size

1.4 GB

Last updated

4 months ago

docker pull eworkerinc/remote-runner