Documentation Index
Fetch the complete documentation index at: https://docs.vers.sh/llms.txt
Use this file to discover all available pages before exploring further.
VM Access Guide
This guide covers how to interact with your Vers VMs - connecting via SSH, executing commands, and transferring files.
Connection Architecture
Vers uses SSH-over-TLS for all VM connections. This means your SSH traffic is tunneled through a TLS connection on port 443.
Your Machine Vers Infrastructure
│ │
│ TLS Connection (port 443) │
├──────────────────────────────────►│ {vm-id}.vm.vers.sh
│ │
│ SSH Handshake (over TLS) │
├──────────────────────────────────►│
│ │
│ Encrypted SSH Session │
│◄─────────────────────────────────►│ Your VM
Benefits:
- Works through corporate firewalls (uses HTTPS port 443)
- Double encryption (TLS + SSH)
- No need to expose SSH ports
- Automatic routing to your specific VM
Ways to Access Your VM
Vers provides three main ways to interact with VMs:
| Method | Command | Use Case |
|---|
| Interactive Shell | vers connect | Development, debugging, manual work |
| Execute Commands | vers execute | Automation, scripts, CI/CD |
| File Transfer | vers copy | Upload/download files and directories |
Interactive Shell (vers connect)
Open a full terminal session to your VM:
# Connect to current HEAD VM
vers connect
# Connect to specific VM
vers connect my-vm
Once connected, you have full root access:
root@vm-abc123:~# apt update
root@vm-abc123:~# npm install
root@vm-abc123:~# python app.py
Terminal Features
- Full PTY support: Run vim, htop, or any interactive program
- Terminal resizing: Window size changes are reflected in the VM
- Color support: Full xterm-256color terminal
- Multiple sessions: Connect from multiple terminals simultaneously
Exiting
Type exit or press Ctrl+D to disconnect. Your VM keeps running.
Executing Commands (vers execute)
Run commands without an interactive session:
# Basic command
vers execute my-vm "ls -la"
# Run a script
vers execute my-vm "python /app/test.py"
# Chain commands
vers execute my-vm "cd /app && npm test"
Output is captured and returned to your terminal. Exit codes are preserved.
Use Cases
Running tests:
vers execute test-vm "npm test"
echo "Exit code: $?"
Automation scripts:
#!/bin/bash
vers execute build-vm "make clean && make build"
vers execute test-vm "make test"
vers execute deploy-vm "make deploy"
Checking status:
vers execute my-vm "systemctl status postgresql"
File Transfer (vers copy)
Transfer files between your machine and VMs using SCP:
# Upload file to VM
vers copy ./local-file.txt my-vm:/remote/path/
# Download file from VM
vers copy my-vm:/remote/file.txt ./local-path/
# Upload directory (recursive)
vers copy -r ./local-dir/ my-vm:/remote/dir/
# Download directory (recursive)
vers copy -r my-vm:/remote/dir/ ./local-dir/
Path Detection
The CLI automatically detects transfer direction:
- Paths starting with
/ are treated as remote (VM) paths
- Other paths are treated as local paths
- If ambiguous, the CLI checks if a local file exists
Examples
Deploy code to VM:
vers copy -r ./src/ my-vm:/app/src/
vers execute my-vm "cd /app && npm install"
Download logs:
vers copy my-vm:/var/log/app.log ./logs/
Backup database:
vers execute my-vm "pg_dump mydb > /tmp/backup.sql"
vers copy my-vm:/tmp/backup.sql ./backups/
SSH Key Management
Vers automatically manages SSH keys for you:
- First connection: CLI fetches your VM’s private key from the API
- Local caching: Key stored at
/tmp/vers-ssh-keys/{vm-id}.key
- Secure permissions: Keys have 0600 permissions (owner read/write only)
- Automatic authentication: Key used for all subsequent connections
You never need to manually configure SSH keys.
Key Storage Location
/tmp/vers-ssh-keys/
├── vm-abc123.key
├── vm-def456.key
└── vm-ghi789.key
Keys are cached per-VM and reused across connections.
Connection Details
Authentication
- Username: Always
root
- Method: Public key authentication (no passwords)
- Keys: Per-VM keys, automatically managed
Network
- Host:
{vm-id}.vm.vers.sh
- Port: 443 (TLS)
- Protocol: SSH tunneled over TLS
Keep-Alive
Connections use automatic keep-alive:
- Interval: 10 seconds
- Max missed: 6 (disconnects after ~60 seconds of no response)
Working with HEAD
Many commands work with your current HEAD VM by default:
# Set HEAD to a VM
vers checkout my-vm
# These all operate on HEAD
vers connect # Connect to HEAD
vers execute "ls" # Execute on HEAD (requires VM ID)
vers copy file /path # Copy to HEAD (requires VM ID)
Check your current HEAD:
vers checkout
Current HEAD: my-vm (State: Running)
Multiple VM Workflows
Parallel Development
# Terminal 1: Frontend development
vers connect frontend-vm
root@frontend:~# npm run dev
# Terminal 2: Backend development
vers connect backend-vm
root@backend:~# python manage.py runserver
# Terminal 3: Database work
vers connect db-vm
root@db:~# psql -U postgres
Test Isolation
# Create test branches
vers branch --alias test-a
vers branch --alias test-b
# Run different tests in parallel
vers execute test-a "npm test -- --suite=unit"
vers execute test-b "npm test -- --suite=integration"
Troubleshooting
Connection Refused
# Error: connection refused
Cause: VM may still be starting up
Solution: Wait a few seconds and retry
VM Not Running
# Error: VM is not running (current state: Paused)
Solution: Resume the VM with vers resume
SSH Key Errors
# Error: failed to get or create SSH key
Cause: Authentication issue
Solution: Run vers login to re-authenticate
Timeout
# Error: context deadline exceeded
Cause: Network issues or VM unavailable
Solution: Check your internet connection; verify VM exists with vers status
Security Best Practices
- Keys are temporary: SSH keys are stored in
/tmp and may be cleared on reboot
- Per-VM isolation: Each VM has its own unique key
- No key sharing: Don’t copy or share VM SSH keys
- Session logging: Your commands run as root - be careful with destructive operations
TypeScript SDK Access
For programmatic VM access, the vers-sdk package provides an API client and a built-in SSH-over-TLS client.
Setup
import { VersSdkClient, connectVmSSH } from 'vers-sdk';
const client = new VersSdkClient({
apiKey: process.env.VERS_API_KEY,
});
Connecting via SSH
Use connectVmSSH to get an SSHClient for a VM. It fetches the SSH key from the API automatically:
const ssh = await connectVmSSH(client, vmId, `${vmId}.vm.vers.sh`);
Execute Commands
Run commands and capture output:
const ssh = await connectVmSSH(client, vmId, `${vmId}.vm.vers.sh`);
const result = await ssh.execute('ls -la /app');
console.log(result.stdout);
console.log('Exit code:', result.exitCode);
if (result.exitCode !== 0) {
console.error('Command failed:', result.stderr);
}
ssh.close();
Streaming Output
For long-running commands, stream output in real-time:
const ssh = await connectVmSSH(client, vmId, `${vmId}.vm.vers.sh`);
const exitCode = await ssh.executeStream('apt update && apt upgrade -y', {
stdout: process.stdout,
stderr: process.stderr,
});
console.log('Finished with exit code:', exitCode);
ssh.close();
File Transfer (SFTP)
Upload and download files:
const ssh = await connectVmSSH(client, vmId, `${vmId}.vm.vers.sh`);
// Upload a single file
await ssh.upload('./local-file.txt', '/remote/path/file.txt');
// Download a single file
await ssh.download('/remote/path/file.txt', './local-file.txt');
// Upload directory recursively with progress
await ssh.upload('./my-app', '/opt/my-app', {
recursive: true,
onProgress: (transferred, total, filename) => {
console.log(`${filename}: ${transferred}/${total} bytes`);
},
});
// Download directory recursively
await ssh.download('/var/log/myapp', './logs', {
recursive: true,
});
ssh.close();
Interactive Shell Sessions
Open an interactive terminal:
const ssh = await connectVmSSH(client, vmId, `${vmId}.vm.vers.sh`);
const session = await ssh.shell({
cols: process.stdout.columns || 80,
rows: process.stdout.rows || 24,
});
// Pipe stdin/stdout for interactive use
process.stdin.pipe(session.stdin);
session.stdout.pipe(process.stdout);
session.stderr.pipe(process.stderr);
// Handle terminal resize
process.stdout.on('resize', () => {
session.resize(process.stdout.columns, process.stdout.rows);
});
// Wait for session to end
await session.wait();
ssh.close();
Reusable Connections
connectVmSSH returns an SSHClient that keeps the connection open. Reuse it for multiple operations:
const ssh = await connectVmSSH(client, vmId, `${vmId}.vm.vers.sh`);
try {
await ssh.execute('apt update');
await ssh.execute('apt install -y nginx');
await ssh.upload('./nginx.conf', '/etc/nginx/nginx.conf');
await ssh.execute('systemctl restart nginx');
const status = await ssh.execute('systemctl status nginx');
console.log(status.stdout);
} finally {
ssh.close();
}
Connection Options
SSH methods accept options for timeouts and cancellation:
const ssh = await connectVmSSH(client, vmId, `${vmId}.vm.vers.sh`, {
timeout: 60000, // Connection timeout (ms)
keepAliveInterval: 10000, // Keep-alive interval (ms)
keepAliveMaxCount: 6, // Max missed keep-alives before disconnect
});
const result = await ssh.execute('long-running-command', {
signal: abortController.signal, // AbortSignal for cancellation
});
Waiting for SSH Availability
After creating a VM, wait for SSH to become available:
async function waitForSSH(
client: VersSdkClient,
vmId: string,
maxAttempts = 30,
) {
for (let i = 0; i < maxAttempts; i++) {
try {
const ssh = await connectVmSSH(client, vmId, `${vmId}.vm.vers.sh`);
const result = await ssh.execute('echo ready');
ssh.close();
if (result.exitCode === 0) return;
} catch {
console.log(`Waiting for SSH... (${i + 1}/${maxAttempts})`);
await new Promise(r => setTimeout(r, 2000));
}
}
throw new Error('SSH not available after timeout');
}
// Usage
const { vm_id } = await client.createNewRootVm({
vm_config: { mem_size_mib: 512, vcpu_count: 1 },
});
await waitForSSH(client, vm_id);
console.log('VM ready for SSH!');
Complete Example
import { VersSdkClient, connectVmSSH } from 'vers-sdk';
async function deployApp() {
const client = new VersSdkClient();
// Create a VM
const { vm_id: vmId } = await client.createNewRootVm({
vm_config: { mem_size_mib: 1024, vcpu_count: 2 },
});
const ssh = await connectVmSSH(client, vmId, `${vmId}.vm.vers.sh`);
try {
// Deploy application
await ssh.upload('./dist', '/opt/app', { recursive: true });
// Install dependencies and start
await ssh.executeStream('cd /opt/app && npm install', {
stdout: process.stdout,
});
await ssh.execute('cd /opt/app && npm start &');
console.log('Application deployed!');
} finally {
ssh.close();
await client.deleteVm(vmId);
}
}
See Also