The Kernel CLI's kernel browsers ssh command (kernel/cli#103) provisions an SSH server on a browser VM and tunnels the connection over WebSockets. Because it sets up a standard OpenSSH server, you can use scp (and rsync, sftp, etc.) with the same tunnel — not just interactive shells.
-
Kernel CLI v0.14.5+ (with the
browsers sshsubcommand and-o jsonsupport) -
websocat — WebSocket-to-TCP bridge used as the SSH ProxyCommand
# macOS brew install websocat # Linux curl -fsSL https://github.com/vi/websocat/releases/download/v1.13.0/websocat.x86_64-unknown-linux-musl \ -o /usr/local/bin/websocat && chmod +x /usr/local/bin/websocat
-
jq — for parsing JSON output in scripts
# macOS brew install jq # Linux apt-get install jq
-
An SSH key pair (ed25519 recommended). If you don't already have one:
ssh-keygen -t ed25519 -f ~/.ssh/kernel_scp -N ""
export KERNEL_API_KEY="sk_..."
export KERNEL_BASE_URL="https://api.onkernel.com"
kernel browsers create --timeout 300
# Note the Session ID from the output, e.g. zujjxiinpe43b85g7574wlxaUse --setup-only -o json with your SSH key so the command provisions the SSH server and returns machine-readable connection details without opening an interactive session:
kernel browsers ssh <SESSION_ID> -i ~/.ssh/kernel_scp --setup-only -o jsonThis outputs a JSON object with everything you need:
{
"vm_domain": "aged-frog-c2zy61hq.prod-iad-ukp-browsers-0.onkernel.app",
"session_id": "zujjxiinpe43b85g7574wlxa",
"ssh_key_file": "/home/user/.ssh/kernel_scp",
"proxy_command": "websocat --binary wss://aged-frog-c2zy61hq.prod-iad-ukp-browsers-0.onkernel.app:2222",
"ssh_command": "ssh -o 'ProxyCommand=websocat --binary wss://...:2222' -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i /home/user/.ssh/kernel_scp root@localhost"
}Use the proxy_command from the JSON output:
VM_DOMAIN=$(echo "$SETUP" | jq -r '.vm_domain')
scp \
-o "ProxyCommand=websocat --binary wss://${VM_DOMAIN}:2222" \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-i ~/.ssh/kernel_scp \
/path/to/local/file.bin \
root@localhost:/tmp/file.binDownload works the same way — just swap the arguments:
scp \
-o "ProxyCommand=websocat --binary wss://${VM_DOMAIN}:2222" \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-i ~/.ssh/kernel_scp \
root@localhost:/tmp/remote-file.bin \
/path/to/local/download.binkernel browsers delete <SESSION_ID>export KERNEL_API_KEY="sk_..."
export KERNEL_BASE_URL="https://api.onkernel.com"
# Generate a one-time key
ssh-keygen -t ed25519 -f /tmp/kernel-scp-key -N "" -q
# Create a browser (grab the session ID)
SESSION_ID=$(kernel browsers create --timeout 300 -o json | jq -r '.session_id')
echo "Session: $SESSION_ID"
# Provision SSH on the VM and extract the VM domain
SETUP=$(kernel browsers ssh "$SESSION_ID" -i /tmp/kernel-scp-key --setup-only -o json)
VM_DOMAIN=$(echo "$SETUP" | jq -r '.vm_domain')
echo "VM domain: $VM_DOMAIN"
# Upload a file
scp -o "ProxyCommand=websocat --binary wss://${VM_DOMAIN}:2222" \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-i /tmp/kernel-scp-key \
./my-large-file.tar.gz root@localhost:/tmp/my-large-file.tar.gz
# Download a file
scp -o "ProxyCommand=websocat --binary wss://${VM_DOMAIN}:2222" \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-i /tmp/kernel-scp-key \
root@localhost:/tmp/result.zip ./result.zip
# Clean up
kernel browsers delete "$SESSION_ID"
rm -f /tmp/kernel-scp-key /tmp/kernel-scp-key.pubSince full SSH is available, rsync works too — useful for syncing directories or resumable transfers:
rsync -avz --progress \
-e "ssh -o 'ProxyCommand=websocat --binary wss://<VM_DOMAIN>:2222' \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-i ~/.ssh/kernel_scp" \
./local-dir/ root@localhost:/tmp/remote-dir/- SSH setup is persistent: once you run
--setup-only, SSH stays running for the lifetime of the browser session. You canscpas many times as you want without re-running setup. - Timeout: set
--timeouthigh enough onkernel browsers createto cover your transfer time. The maximum is 259200 (72 hours). - Performance: in testing, a 50 MB file transfers in ~1.5 seconds over the WebSocket tunnel. Throughput is roughly comparable to a direct SSH connection since
websocatis just bridging TCP over a WebSocket. - Integrity: checksums match between source and destination — the WebSocket tunnel is binary-safe.
- Headless mode: SSH works with both headless (
--headless) and non-headless browser sessions.
Local Machine Kernel Browser VM
┌──────────┐ WebSocket (wss) ┌────────────────────┐
│ scp │◄──────────────────────►│ websocat (:2222) │
│ └─ssh │ via ProxyCommand │ └─► sshd (:22) │
│ └─key │ │ └─► filesystem│
└──────────┘ └────────────────────┘
kernel browsers ssh --setup-only installs and starts sshd + websocat on the VM via the Kernel process exec API. websocat listens on port 2222 and bridges WebSocket connections to sshd on port 22. The local scp/ssh command connects through websocat as a ProxyCommand.
This repo also contains smoke-test.ts, a TypeScript smoke test for the new batch computer controls API in @onkernel/sdk v0.31.0. Run it with:
KERNEL_API_KEY="sk_..." KERNEL_BASE_URL="https://api.onkernel.com" npx tsx smoke-test.ts