Skip to content

Where VMs Run

A sand VM doesn't have to run on the machine in front of you. It can run:

  • On this machine — the default. sand runs Lima locally, and nothing needs configuring.
  • On another machine, over SSHsand runs the same Lima commands on a host you can SSH into, so VMs live on a bigger box (a workstation, a home server) while you work from a laptop.
  • On a Proxmox VE hostsand drives VMs through the Proxmox REST API with a pool-scoped token. No Lima involved. See Proxmox VE Setup for the one-time host setup.

Whichever you pick, the commands, the board, and the keybindings are the same. Only the machine underneath changes.

Every location sand knows about is a Connection Profile. Profiles are the only configuration surface for this: there is no environment variable to set and no per-invocation flag needed to pick a backend (older sand builds used SAND_PROVIDER / SAND_REMOTE_* environment variables for this; those are removed — see Environment variables removed below).

What a profile is

A profile has:

  • A name — a short, renameable label you pick (work, home-server, local). Names don't need to be unique, though the TUI won't stop you from picking a name that collides with another profile's — it's a display label, not an identifier.
  • A typelocal, remote-ssh, or proxmox.
  • Connection details — for remote-ssh: host, SSH user, port, an optional path to a private key file, and an optional remote LIMA_HOME. Setting a profile's lima_home scopes both the remote instance discovery (determining which instances limactl list returns) and file-based state reads (base version stamp, provenance markers). Different profiles can point at the same remote host but use different LIMA_HOME paths, isolating their instances. For proxmox: host, node, pool, storage, bridge, and a path to a token file — see Proxmox VE for the full field list and the one-time host setup that scopes the API token to a dedicated pool.
  • An enabled flag.

There is always exactly one Local profile — permanent, created automatically the first time sand runs, and not deletable (though you can rename it). Every other profile is remote-ssh or proxmox, and you can create, edit, enable, disable, or delete as many of those as you like.

profiles.yaml

Profiles are persisted at:

${XDG_CONFIG_HOME:-~/.config}/sandbar/profiles.yaml

This file is secret-free — it holds a host/user/port/key-path, never a password or key material — so it's safe to check into a dotfiles repo or copy between machines: the same profiles.yaml on your laptop and your desktop gives both clients the same set of named locations. It's also plain, hand-editable YAML, shaped like this:

version: 1
last_used: a1b2c3d4e5f6a7b8
profiles:
  - id: local
    name: local
    type: local
    enabled: true
  - id: a1b2c3d4e5f6a7b8
    name: work
    type: remote-ssh
    enabled: true
    host: 192.168.1.100
    user: debian
    port: 22
    identity_path: /home/me/.ssh/id_work
    lima_home: /home/debian/.lima

id is assigned once at creation and never changes, even across a rename — it's how sand tracks "the last profile you used" and which managed VMs belong to which profile without losing track when you rename one. name is the label you see and address the profile by from the CLI (--profile) and in the TUI.

lima_home (optional, remote profiles only) is the remote host's LIMA_HOME directory (where Lima stores instance state). If unset, it defaults to .lima (Lima's own default, relative to the remote user's home directory). Setting lima_home affects both discovery and file reads: every limactl command and provenance marker lookup on the remote host uses this directory. This lets you isolate instances on a shared host by pointing different profiles at different LIMA_HOME paths.

Two enabled remote-ssh profiles may not point at the same user@host:portsand refuses to save that, so the same physical target is never double-counted as two separate fleet members. There can also only ever be one local profile.

Managing profiles

In the TUI

Press p from the board to open the profile management screen: a list of every profile, its type, and its enabled/disabled state.

Key Action
Move between profiles
enter Edit the selected profile (or create a new one from the ghost row)
t Toggle enabled/disabled
d Delete (not offered for Local)

When creating a profile (press n), a type picker first asks whether to create a Remote SSH or a Proxmox profile, then opens the matching form. (Local is not offered — there is always exactly one Local profile, created for you and never deletable.) Each form covers every field of its type, so nothing needs hand-editing afterwards. Whichever field has focus, its description appears below the form — including, for an optional field, the default it falls back to when you leave it blank.

Every change here is live — there is no restart, and no separate "apply" step:

  • Enabling a profile immediately builds its connection and starts listing its VMs; its tiles appear on the board as soon as that finishes.
  • Disabling a profile tears its connection down and shows a banner in the header instead of its tiles, but leaves its managed-VM records and secrets exactly as they were — re-enable it later and everything comes back.
  • Deleting a non-Local profile removes it from profiles.yaml and drops its tiles from the board. It never touches the remote host — no VM is stopped or deleted, no SSH command is run against it. Deleting a profile only forgets that sand should manage that connection; if you want to re-adopt the same host later, create a new profile pointing at it.
  • Renaming a profile (a pure name edit, connection unchanged) doesn't rebuild anything — its VMs, jobs, and last-used pointer all follow the rename by ID.

Editing a profile's connection fields (its host, credentials, and the rest), or disabling/deleting it, is only offered while that profile is idle — no build or other job currently running against it — the same guard that protects a single VM from a destructive action mid-build, generalized to the whole profile.

The VM create form also has a profile selector, so you choose which enabled profile a new VM is created on right from the form, without leaving the TUI.

From the CLI

sand create --profile <name> picks which profile a headless create acts on. sand shell NAME --profile <name> disambiguates when a VM named NAME exists on more than one enabled profile. See the CLI Reference for the full flag semantics and resolution order.

All enabled profiles are active at once

Unlike the old environment-variable selection, which chose exactly one backend per process, every enabled profile is live simultaneously in the TUI: the board shows tiles from this machine, every enabled remote host, and every Proxmox host side by side, each tile labeled with the profile it runs on (see The TUI). A disabled or errored profile shows a banner instead of tiles, naming the profile and the reason.

Because VMs are tracked per-profile, the same VM name can exist under two different profiles without conflict — claude on your local profile and claude on your work profile are two independent VMs that happen to share a display name. sand shell claude is ambiguous in that case and asks you to disambiguate with --profile.

Requirements on a remote SSH host

These apply to a remote-ssh profile. A Proxmox profile has its own, different setup — see Proxmox VE Setup.

  • Lima (limactl on PATH) — the remote host runs the exact same limactl the local provider does; sand only changes where it runs.
  • A working hypervisor (QEMU/KVM, etc.), just like any Linux host running Lima — see the Lima installation docs.
  • Passwordless SSH to the target (key-based auth). sand runs limactl non-interactively over SSH, so the connection must not prompt.

What changes when the VM isn't on your machine

Not much. With any profile enabled, sand create --profile, the TUI, sand shell, and file copy work the way they do locally — the base image is built on that host once, each VM is a clone of it, and finalize runs there too. Three things are worth knowing:

  • Interactive shells (sand shell NAME and S on a tile) wrap the guest tmux attach with ssh -t automatically; detaching still leaves the session running, exactly as with a local VM (see Files and Shells).
  • File transfer works in both directions, but the route differs. On a remote-ssh profile the remote limactl copy can't see your local filesystem, so sand stages the file through the remote host and preserves where it lands in the guest. On a proxmox profile sand copies straight to the guest with scp.
  • Reaching a web server in the guest differs the same way. On a remote-ssh profile, Lima forwards the guest's ports to the remote host's loopback, not yours, so they aren't reachable from your machine without a tunnel. On a proxmox profile the guest has its own address on your network, so you connect to it directly. See Web Servers and Ports.

Isolation. VMs created on a profile are tagged with that profile's connection in the managed-VM index, so they never mix with VMs from another profile — a local limactl list and a remote profile's list never show each other's instances. The index records only a profile's user@host:port (or host:node/pool), never a private key, password, or API token.

Environment variables removed

Earlier (unreleased) builds of sand selected a single remote target for the whole process via SAND_PROVIDER, SAND_REMOTE_HOST, SAND_REMOTE_USER, SAND_REMOTE_PORT, SAND_REMOTE_IDENTITY, and SAND_REMOTE_LIMA_HOME environment variables. That surface has been removed entirely and replaced by Connection Profiles: set these variables and sand will simply ignore them. If you were using them, create an equivalent remote-ssh profile instead (in the TUI's p screen, or by hand-editing profiles.yaml) — see profiles.yaml above for the field mapping (SAND_REMOTE_HOSThost, SAND_REMOTE_USERuser, SAND_REMOTE_PORTport, SAND_REMOTE_IDENTITYidentity_path, SAND_REMOTE_LIMA_HOMElima_home).

More backends later

The three profile types are three implementations of one internal seam in sand, which is what lets them share every command and screen. Further backends (DigitalOcean, Linode) can be added the same way; none are available yet.