Skip to content
Select themeSelect language

SSH execution backends

By default a runner runs each claimed task in a container on its own local Docker host (SUPACLOUD_RUNNER_EXECUTION_BACKEND=docker). ADR 0046 Phase 3 adds two SSH execution backends so a runner can instead drive a remote host. Pick a backend with SUPACLOUD_RUNNER_EXECUTION_BACKEND:

Value What it does Isolation Status
docker Local Docker host (the default). Full container caps. GA
ssh-docker Remote Docker daemon over SSH. Full — identical container caps on the remote host. Experimental (see caveats)
raw-ssh-exec Agent as a raw remote process over SSH. None — no container, no resource caps. Experimental, double-gated
Section titled “Recommended: ssh-docker (remote Docker, isolation preserved)”

A DockerManager pointed at a remote daemon makes this backend run each task through the same code path as a local run — so cap_drop=ALL, no-new-privileges, a read-only rootfs, tmpfs mounts and the memory/CPU/pids limits are all preserved on the remote host.

  1. Install an SSH client on the runner host. bollard’s SSH transport shells out to the system ssh binary (docker system dial-stdio). The SupaCloud server image ships openssh-client; a bespoke runner image may not — install it (apt-get install -y openssh-client) or the backend cannot connect.

  2. Configure host-key verification in your system ssh client (mandatory). For ssh-docker, SupaCloud cannot enforce the host key itself — bollard’s SSH transport shells out to the system ssh binary and exposes only a key-file path, not -o options. You must configure that client to pin the host, then acknowledge the delegation. Add the remote host’s key to a known_hosts file and point the runner’s ssh at it with strict checking, e.g. in the runner user’s ~/.ssh/config:

    Host remote-host.example
    StrictHostKeyChecking yes
    UserKnownHostsFile /etc/supacloud/known_hosts

    Then set the backend env, including the explicit delegation acknowledgment:

    SUPACLOUD_RUNNER_EXECUTION_BACKEND=ssh-docker
    SUPACLOUD_SSH_HOST=ssh://docker@remote-host.example:22
    SUPACLOUD_SSH_KEY_PATH=/etc/supacloud/id_ed25519
    SUPACLOUD_SSH_KNOWN_HOSTS=/etc/supacloud/known_hosts
    SUPACLOUD_SSH_DOCKER_HOSTKEY_DELEGATED=true

    The runner refuses to start without both SUPACLOUD_SSH_KNOWN_HOSTS (existing, non-empty) and SUPACLOUD_SSH_DOCKER_HOSTKEY_DELEGATED=true.

  • Live event relay. The agent bridge resolves only on the remote host’s agent network, so live UI progress needs an ssh -L forward that v1 does not set up. The run still reports done/failed correctly — only the live stream is absent.
  • Live round-trip is not in CI. There is no remote Docker daemon (or system ssh) in the test image, so the end-to-end remote run is validated only by an env-gated test (SUPACLOUD_TEST_SSH_DOCKER_HOST).

This backend runs the agent as a bare remote process — no container, no resource caps, no event relay. It exists only to satisfy the ADR 0046 decision to ship both SSH variants. Prefer ssh-docker unless you specifically need it.

It is double-gated — both opt-ins are required and it fails closed otherwise:

  1. GATE 1 (runner boot): SUPACLOUD_RUNNER_ALLOW_UNSANDBOXED=true. Selecting raw-ssh-exec without it is a hard boot error.
  2. GATE 2 (dispatch): the runner must advertise capabilities.backends: ["ssh-exec"] at registration and the task must resolve to the ssh-exec backend. A default (docker) task never lands on an ssh-exec-only runner, and an ssh-exec task never lands on a docker-only runner.

Because the work payload carries no agent entrypoint (it normally lives in the container image), you must supply a curated launch command:

SUPACLOUD_RUNNER_EXECUTION_BACKEND=raw-ssh-exec
SUPACLOUD_RUNNER_ALLOW_UNSANDBOXED=true
SUPACLOUD_SSH_HOST=ssh://agent@remote-host.example:22
SUPACLOUD_SSH_KEY_PATH=/etc/supacloud/id_ed25519
SUPACLOUD_SSH_KNOWN_HOSTS=/etc/supacloud/known_hosts
SUPACLOUD_SSH_EXEC_AGENT_CMD="run-agent --image {image} --type {agent_type}"

{image} and {agent_type} are substituted shell-quoted, and the task’s environment is exported through a quoted prologue — every value is treated as untrusted and escaped, so a hostile image name or env value cannot inject a command.