Skip to main content

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:
MethodCommandUse Case
Interactive Shellvers connectDevelopment, debugging, manual work
Execute Commandsvers executeAutomation, scripts, CI/CD
File Transfervers copyUpload/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:
  1. First connection: CLI fetches your VM’s private key from the API
  2. Local caching: Key stored at /tmp/vers-ssh-keys/{vm-id}.key
  3. Secure permissions: Keys have 0600 permissions (owner read/write only)
  4. 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

  1. Keys are temporary: SSH keys are stored in /tmp and may be cleared on reboot
  2. Per-VM isolation: Each VM has its own unique key
  3. No key sharing: Don’t copy or share VM SSH keys
  4. Session logging: Your commands run as root - be careful with destructive operations

TypeScript SDK Access

For programmatic VM access, the Vers TypeScript SDK provides full SSH functionality through the withSSH wrapper.

Setup

import Vers, { withSSH } from 'vers';

const client = new Vers({
  apiKey: process.env.VERS_API_KEY,
});

// Wrap the VM resource with SSH capabilities
const vm = withSSH(client.vm);

Execute Commands

Run commands and capture output:
// Simple command execution
const result = await vm.execute(vmId, 'ls -la /app');
console.log(result.stdout);
console.log('Exit code:', result.exitCode);

// Check for errors
if (result.exitCode !== 0) {
  console.error('Command failed:', result.stderr);
}

Streaming Output

For long-running commands, stream output in real-time:
// Stream output to console
const exitCode = await vm.executeStream(vmId, 'apt update && apt upgrade -y', {
  stdout: process.stdout,
  stderr: process.stderr,
});

console.log('Finished with exit code:', exitCode);

File Transfer (SFTP)

Upload and download files:
// Upload a single file
await vm.upload(vmId, './local-file.txt', '/remote/path/file.txt');

// Download a single file
await vm.download(vmId, '/remote/path/file.txt', './local-file.txt');

// Upload directory recursively with progress
await vm.upload(vmId, './my-app', '/opt/my-app', {
  recursive: true,
  onProgress: (transferred, total, filename) => {
    console.log(`${filename}: ${transferred}/${total} bytes`);
  },
});

// Download directory recursively
await vm.download(vmId, '/var/log/myapp', './logs', {
  recursive: true,
});

Interactive Shell Sessions

Open an interactive terminal:
const session = await vm.connect(vmId, {
  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();

Reusable Connections

For multiple operations, reuse a single SSH connection:
const ssh = await vm.getClient(vmId);

try {
  // Run multiple commands on the same connection
  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 {
  // Always close when done
  ssh.close();
}

Connection Options

All SSH methods accept options for timeouts, retries, and cancellation:
const result = await vm.execute(vmId, 'long-running-command', {
  timeout: 60000,           // Connection timeout (ms)
  keepAliveInterval: 10000, // Keep-alive interval (ms)
  keepAliveMaxCount: 6,     // Max missed keep-alives before disconnect
  retries: 3,               // Number of connection retries
  retryDelay: 1000,         // Delay between retries (ms)
  signal: abortController.signal, // AbortSignal for cancellation
});

SSH Key Caching

The SDK caches SSH keys in memory for efficiency:
// Clear cache for a specific VM (e.g., after VM restart)
vm.clearKeyCache(vmId);

// Clear all cached keys
vm.clearKeyCache();

Waiting for SSH Availability

After creating a VM, wait for SSH to become available:
async function waitForSSH(vm, vmId, maxAttempts = 30) {
  for (let i = 0; i < maxAttempts; i++) {
    try {
      const result = await vm.execute(vmId, 'echo ready');
      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.vm.createRoot({ /* config */ });
await waitForSSH(vm, vm_id);
console.log('VM ready for SSH!');

Complete Example

import Vers, { withSSH } from 'vers';

async function deployApp() {
  const client = new Vers();
  const vm = withSSH(client.vm);

  // Create a VM
  const { vm_id: vmId } = await client.vm.createRoot({
    vm_config: { mem_size_mib: 1024, vcpu_count: 2 },
  });

  try {
    // Wait for SSH
    await waitForSSH(vm, vmId);

    // Deploy application
    await vm.upload(vmId, './dist', '/opt/app', { recursive: true });

    // Install dependencies and start
    await vm.executeStream(vmId, 'cd /opt/app && npm install', {
      stdout: process.stdout,
    });

    await vm.execute(vmId, 'cd /opt/app && npm start &');

    console.log('Application deployed!');
  } finally {
    // Cleanup
    await client.vm.delete(vmId);
  }
}

See Also