Skip to content

Commit 36e7ae8

Browse files
committed
Add SSH access template documentation
Adds a new template example showing how to enable SSH access to E2B sandboxes using websocat as a WebSocket-to-SSH proxy. This is a manual workaround while native SSH support is in development. Includes: - Template definition for SSH-ready sandbox - Quick start guide for CLI users - Connection methods (websocat and curl) - SSH config shortcut for easier access - SSH key authentication setup - SCP file transfer examples
1 parent c1df0cc commit 36e7ae8

2 files changed

Lines changed: 341 additions & 1 deletion

File tree

docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@
120120
"docs/template/examples/nextjs-bun",
121121
"docs/template/examples/expo",
122122
"docs/template/examples/desktop",
123-
"docs/template/examples/claude-code"
123+
"docs/template/examples/claude-code",
124+
"docs/template/examples/ssh-ready"
124125
]
125126
},
126127
"docs/template/migration-v2",
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
---
2+
title: "SSH Access"
3+
description: "Connect to your sandbox via SSH using a WebSocket proxy"
4+
---
5+
6+
<Warning>
7+
This is a manual workaround. Native SSH support is planned but not yet available in E2B.
8+
This method uses websocat to proxy SSH connections over WebSocket through the sandbox's exposed ports.
9+
</Warning>
10+
11+
SSH access enables remote terminal sessions, SCP/SFTP file transfers, and integration with tools that expect SSH connectivity. This guide shows how to create a template with SSH server and WebSocket proxy pre-configured.
12+
13+
<Note>
14+
For simpler interactive terminal use cases, consider using the [PTY module](/docs/sandbox/pty) instead—it provides real-time bidirectional terminal communication without external SSH clients.
15+
</Note>
16+
17+
## How it works
18+
19+
```
20+
┌─────────────────────────────────────────────────────────────┐
21+
│ Your Machine │
22+
│ ┌──────────┐ ProxyCommand ┌──────────────────┐ │
23+
│ │ SSH │ ──────────────────── │ websocat / curl │ │
24+
│ │ Client │ │ (WebSocket) │ │
25+
│ └──────────┘ └────────┬─────────┘ │
26+
└─────────────────────────────────────────────┼──────────────┘
27+
28+
wss://8081-<sandbox-id>.e2b.app
29+
30+
┌─────────────────────────────────────────────┼──────────────┐
31+
│ E2B Sandbox ▼ │
32+
│ ┌──────────────────┐ │
33+
│ │ websocat │ │
34+
│ │ (WS → TCP:22) │ │
35+
│ └────────┬─────────┘ │
36+
│ │ │
37+
│ ┌────────▼─────────┐ │
38+
│ │ SSH Server │ │
39+
│ │ (OpenSSH) │ │
40+
│ └──────────────────┘ │
41+
└─────────────────────────────────────────────────────────────┘
42+
```
43+
44+
The WebSocket connection tunnels through E2B's port forwarding (`8081-<sandbox-id>.e2b.app`), and websocat inside the sandbox bridges it to the local SSH server.
45+
46+
## Template definition
47+
48+
<CodeGroup>
49+
50+
```typescript JavaScript & TypeScript
51+
// template.ts
52+
import { Template, waitForPort } from 'e2b'
53+
54+
export const template = Template()
55+
.fromUbuntuImage('22.04')
56+
.aptInstall(['openssh-server', 'wget'])
57+
// Download websocat binary
58+
.runCmd([
59+
'wget -qO /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl',
60+
'chmod a+x /usr/local/bin/websocat',
61+
])
62+
// Configure SSH
63+
.runCmd([
64+
'mkdir -p /run/sshd',
65+
// Enable password auth (use SSH keys in production)
66+
"sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
67+
// Set default password (change this!)
68+
'echo "user:sandbox" | chpasswd',
69+
])
70+
.copy('start_ssh.sh', '/start_ssh.sh')
71+
.runCmd('chmod +x /start_ssh.sh')
72+
.setStartCmd('/start_ssh.sh', waitForPort(8081))
73+
```
74+
75+
```python Python
76+
# template.py
77+
from e2b import Template, wait_for_port
78+
79+
template = (
80+
Template()
81+
.from_ubuntu_image("22.04")
82+
.apt_install(["openssh-server", "wget"])
83+
# Download websocat binary
84+
.run_cmd([
85+
"wget -qO /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl",
86+
"chmod a+x /usr/local/bin/websocat",
87+
])
88+
# Configure SSH
89+
.run_cmd([
90+
"mkdir -p /run/sshd",
91+
# Enable password auth (use SSH keys in production)
92+
"sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
93+
# Set default password (change this!)
94+
'echo "user:sandbox" | chpasswd',
95+
])
96+
.copy("start_ssh.sh", "/start_ssh.sh")
97+
.run_cmd("chmod +x /start_ssh.sh")
98+
.set_start_cmd("/start_ssh.sh", wait_for_port(8081))
99+
)
100+
```
101+
102+
</CodeGroup>
103+
104+
```bash start_ssh.sh
105+
#!/bin/bash
106+
107+
# Start SSH daemon
108+
/usr/sbin/sshd
109+
110+
# Start WebSocket-to-SSH proxy on port 8081
111+
exec websocat -b --exit-on-eof ws-l:0.0.0.0:8081 tcp:127.0.0.1:22
112+
```
113+
114+
## Build the template
115+
116+
<CodeGroup>
117+
118+
```typescript JavaScript & TypeScript
119+
// build.ts
120+
import { Template, defaultBuildLogger } from 'e2b'
121+
import { template as sshTemplate } from './template'
122+
123+
await Template.build(sshTemplate, 'ssh-ready', {
124+
cpuCount: 2,
125+
memoryMB: 2048,
126+
onBuildLogs: defaultBuildLogger(),
127+
})
128+
```
129+
130+
```python Python
131+
# build.py
132+
from e2b import Template, default_build_logger
133+
from template import template as ssh_template
134+
135+
Template.build(ssh_template, "ssh-ready",
136+
cpu_count=2,
137+
memory_mb=2048,
138+
on_build_logs=default_build_logger(),
139+
)
140+
```
141+
142+
</CodeGroup>
143+
144+
## Quick start (CLI)
145+
146+
You can also set this up manually without building a custom template:
147+
148+
```bash
149+
# Create a sandbox from the base image
150+
e2b sbx create base
151+
152+
# Inside the sandbox, install and start the SSH proxy
153+
sudo apt-get update && sudo apt-get install -y openssh-server
154+
sudo mkdir -p /run/sshd
155+
sudo wget -qO /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl
156+
sudo chmod a+x /usr/local/bin/websocat
157+
158+
# Set a password for the user
159+
echo "user:sandbox" | sudo chpasswd
160+
161+
# Start SSH and the WebSocket proxy
162+
sudo /usr/sbin/sshd
163+
websocat -b --exit-on-eof ws-l:0.0.0.0:8081 tcp:127.0.0.1:22
164+
```
165+
166+
## Connect via SSH
167+
168+
### Install websocat locally
169+
170+
<Tabs>
171+
<Tab title="macOS">
172+
```bash
173+
brew install websocat
174+
```
175+
</Tab>
176+
<Tab title="Linux">
177+
```bash
178+
# Download binary
179+
sudo wget -qO /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl
180+
sudo chmod a+x /usr/local/bin/websocat
181+
```
182+
</Tab>
183+
</Tabs>
184+
185+
### Connect using websocat
186+
187+
```bash
188+
# Replace <sandbox-id> with your actual sandbox ID
189+
ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.e2b.app' <sandbox-id>
190+
```
191+
192+
<Note>
193+
The `<sandbox-id>` serves as both the SSH "host" and is interpolated into the WebSocket URL via `%h`.
194+
Default credentials: username `user`, password `sandbox`.
195+
</Note>
196+
197+
### Connect using curl (alternative)
198+
199+
If you have curl with WebSocket support (macOS Homebrew or recent Linux):
200+
201+
```bash
202+
# macOS with Homebrew curl
203+
ssh -o 'ProxyCommand=/opt/homebrew/opt/curl/bin/curl --no-progress-meter -N --http1.1 -T . wss://8081-%h.e2b.app' <sandbox-id>
204+
205+
# Linux with modern curl (7.86+)
206+
ssh -o 'ProxyCommand=curl --no-progress-meter -N --http1.1 -T . wss://8081-%h.e2b.app' <sandbox-id>
207+
```
208+
209+
### SSH config shortcut
210+
211+
Add this to `~/.ssh/config` for easier access:
212+
213+
```bash
214+
Host *.e2b
215+
User user
216+
ProxyCommand websocat --binary -B 65536 - wss://8081-%h.app
217+
StrictHostKeyChecking no
218+
UserKnownHostsFile /dev/null
219+
```
220+
221+
Then connect with:
222+
223+
```bash
224+
ssh <sandbox-id>.e2b
225+
```
226+
227+
## SDK usage
228+
229+
<CodeGroup>
230+
231+
```typescript JavaScript & TypeScript
232+
import { Sandbox } from 'e2b'
233+
234+
const sbx = await Sandbox.create('ssh-ready')
235+
236+
const host = sbx.getHost(8081)
237+
console.log(`
238+
Sandbox ready!
239+
240+
Connect with:
241+
ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://${host}' user@localhost
242+
243+
Or with curl:
244+
ssh -o 'ProxyCommand=curl --no-progress-meter -N --http1.1 -T . wss://${host}' user@localhost
245+
246+
Password: sandbox
247+
`)
248+
249+
// Keep sandbox alive - it will timeout based on your settings
250+
// await sbx.kill() // Call when done
251+
```
252+
253+
```python Python
254+
from e2b import Sandbox
255+
256+
sbx = Sandbox("ssh-ready")
257+
258+
host = sbx.get_host(8081)
259+
print(f"""
260+
Sandbox ready!
261+
262+
Connect with:
263+
ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://{host}' user@localhost
264+
265+
Or with curl:
266+
ssh -o 'ProxyCommand=curl --no-progress-meter -N --http1.1 -T . wss://{host}' user@localhost
267+
268+
Password: sandbox
269+
""")
270+
271+
# Keep sandbox alive - it will timeout based on your settings
272+
# sbx.kill() # Call when done
273+
```
274+
275+
</CodeGroup>
276+
277+
## Using SSH keys (recommended)
278+
279+
For production use, configure SSH key authentication instead of passwords:
280+
281+
<CodeGroup>
282+
283+
```typescript JavaScript & TypeScript
284+
import { Sandbox } from 'e2b'
285+
286+
const sbx = await Sandbox.create('ssh-ready')
287+
288+
// Inject your public key
289+
const publicKey = 'ssh-rsa AAAA... your-key-here'
290+
await sbx.files.write('/home/user/.ssh/authorized_keys', publicKey)
291+
await sbx.commands.run('chmod 600 /home/user/.ssh/authorized_keys')
292+
await sbx.commands.run('chown -R user:user /home/user/.ssh')
293+
294+
const host = sbx.getHost(8081)
295+
console.log(`Connect with: ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://${host}' user@localhost`)
296+
```
297+
298+
```python Python
299+
from e2b import Sandbox
300+
301+
sbx = Sandbox("ssh-ready")
302+
303+
# Inject your public key
304+
public_key = "ssh-rsa AAAA... your-key-here"
305+
sbx.files.write("/home/user/.ssh/authorized_keys", public_key)
306+
sbx.commands.run("chmod 600 /home/user/.ssh/authorized_keys")
307+
sbx.commands.run("chown -R user:user /home/user/.ssh")
308+
309+
host = sbx.get_host(8081)
310+
print(f"Connect with: ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://{host}' user@localhost")
311+
```
312+
313+
</CodeGroup>
314+
315+
To disable password authentication entirely, modify the template:
316+
317+
```typescript
318+
.runCmd("sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config")
319+
```
320+
321+
## File transfer with SCP
322+
323+
Once SSH is working, you can use SCP for file transfers:
324+
325+
```bash
326+
# Upload a file
327+
scp -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.e2b.app' ./local-file.txt <sandbox-id>:/home/user/
328+
329+
# Download a file
330+
scp -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.e2b.app' <sandbox-id>:/home/user/remote-file.txt ./
331+
```
332+
333+
## Alternatives
334+
335+
| Approach | Best for |
336+
|----------|----------|
337+
| **[PTY module](/docs/sandbox/pty)** | Interactive terminals via SDK, no external tools needed |
338+
| **[E2B CLI](/docs/cli/connect-to-sandbox)** | Quick terminal access during development |
339+
| **SSH (this guide)** | Integration with SSH-based tools, SCP/SFTP, remote IDEs |

0 commit comments

Comments
 (0)