podspawnpodspawn

Client Commands

CLI reference for podspawn client-side commands

Client commands run on the developer's machine. They configure the .pod namespace routing so that ssh alice@work.pod reaches the right server and creates a container.

The client binary is optional. Without it, users SSH directly to the server and still get containers. The client adds .pod namespace resolution and multi-server routing.

setup

Configures ~/.ssh/config with the Host *.pod block that wires up podspawn's ProxyCommand. Creates the file and ~/.ssh/ directory if they don't exist. Idempotent: running it twice does nothing.

podspawn setup

What it writes

Host *.pod
    ProxyCommand podspawn connect %r %h %p
    SetEnv PODSPAWN_PROJECT=%n
    SendEnv PODSPAWN_PROJECT
    UserKnownHostsFile /dev/null
    StrictHostKeyChecking no

SetEnv/SendEnv pass the .pod hostname to the server for project routing. UserKnownHostsFile /dev/null and StrictHostKeyChecking no exist because .pod hostnames are virtual, they all resolve to real servers whose host keys you already trust.

Examples

# First run
$ podspawn setup
added *.pod block to ~/.ssh/config

# Already configured
$ podspawn setup
~/.ssh/config already has Host *.pod block, skipping

If you prefer not to install the client binary, copy the SSH config block above into ~/.ssh/config manually. Or skip the .pod namespace entirely and SSH directly to the server.


config

Show or edit client configuration stored at ~/.podspawn/config.yaml.

podspawn config                          # show current config
podspawn config set <key> <value>        # set a config value

Valid keys

KeyDescription
servers.defaultFallback server for any .pod hostname without a specific mapping
servers.mappings.<host>Map a specific .pod hostname to a server address

Examples

# Set the default server
$ podspawn config set servers.default devbox.company.com
set servers.default = devbox.company.com

# Map gpu.pod to a specific machine
$ podspawn config set servers.mappings.gpu.pod gpu-server.company.com
set servers.mappings.gpu.pod = gpu-server.company.com

# View the current config
$ podspawn config
config: /home/alice/.podspawn/config.yaml

servers.default = devbox.company.com
servers.mappings.gpu.pod = gpu-server.company.com

Config file format

The underlying YAML looks like this:

servers:
  default: devbox.company.com
  mappings:
    work.pod: devbox.company.com
    gpu.pod: gpu-server.company.com
    personal.pod: my-homelab.ddns.net

Output when empty

If no config file exists, podspawn config prints instructions instead of an error:

No client config found. Create one with:
  podspawn config set servers.default yourserver.com

servers

Lists all configured servers with a live connectivity check. Tests TCP reachability on port 22 and reports latency.

podspawn servers

Output format

  (default)                 devbox.company.com  ok (23ms)
  gpu.pod                   gpu-server.company.com  ok (45ms)
  localhost.pod             127.0.0.1  ok (1ms)

localhost.pod is always included, it resolves to 127.0.0.1 without any config. Unreachable servers show unreachable instead of latency.

Example with an unreachable server

  (default)                 devbox.company.com  ok (23ms)
  staging.pod               staging.internal  unreachable
  localhost.pod             127.0.0.1  ok (1ms)

The connectivity check is a TCP probe on port 22 with a 3-second timeout. It does not authenticate or verify that podspawn is installed on the remote host. Use podspawn ping for a deeper check.


ssh

Convenience wrapper around ssh. Appends .pod to bare hostnames (those without dots or colons), then hands off to the system SSH binary.

podspawn ssh <user@host> [-- command...]

Hostname resolution

InputBecomes
alice@backendssh alice@backend.pod
alice@backend.podssh alice@backend.pod (unchanged)
alice@myserver.comssh alice@myserver.com (unchanged, has a dot)

Bare names (no dots, no colons) get .pod appended. Everything else passes through unchanged.

Remote commands

Use -- to separate the remote command from the SSH target:

# Interactive shell
$ podspawn ssh alice@backend

# Run a command
$ podspawn ssh alice@backend -- ls -la /workspace

# Multi-word commands work as expected
$ podspawn ssh alice@backend -- cat /etc/os-release

Flag parsing is disabled for this command. Everything after the target is passed through to SSH, so flags like -v or -L 8080:localhost:8080 do not work. Use ssh directly for advanced SSH flags.


open

Opens VS Code or Cursor with Remote SSH connected to a podspawn container.

podspawn open <user@host> [path]

Flags

FlagDefaultDescription
--cursorfalseOpen in Cursor instead of VS Code

Arguments

ArgumentRequiredDefaultDescription
user@hostYesSSH target (.pod appended to bare names, same as ssh command)
pathNo/workspaceRemote directory to open

Examples

# Open VS Code at /workspace
$ podspawn open alice@backend
opening alice@backend.pod in code at /workspace

# Open a specific directory
$ podspawn open alice@backend /app
opening alice@backend.pod in code at /app

# Use Cursor instead
$ podspawn open alice@backend --cursor
opening alice@backend.pod in cursor at /workspace

What it runs

Under the hood, this executes:

code --remote ssh-remote+alice@backend.pod /workspace

Or with --cursor:

cursor --remote ssh-remote+alice@backend.pod /workspace

The code or cursor binary must be in your PATH. If not found, the error message tells you how to fix it.


ping

Tests connectivity to a podspawn server. Performs a TCP probe on port 22, then attempts an SSH connection to detect the remote podspawn version.

podspawn ping <server>

Arguments

ArgumentDescription
serverHostname, IP address, or .pod name to check

The server argument supports .pod names, which are resolved through the client config before probing.

Output

$ podspawn ping devbox.company.com
pinging devbox.company.com (port 22)...
  tcp connect: 23ms
  ssh round-trip: 187ms
  remote version: podspawn v0.3.0

If SSH authentication fails (expected when probing without valid credentials), the SSH portion reports that TCP is reachable:

$ podspawn ping devbox.company.com
pinging devbox.company.com (port 22)...
  tcp connect: 23ms
  ssh probe failed (auth required; TCP is reachable)

If the server is completely unreachable, the command returns an error:

$ podspawn ping dead-server.com
pinging dead-server.com (port 22)...
Error: unreachable: dial tcp: i/o timeout

.pod name resolution

# These work the same way
$ podspawn ping devbox.company.com
$ podspawn ping work.pod           # resolved via ~/.podspawn/config.yaml
$ podspawn ping localhost.pod      # always resolves to 127.0.0.1

connect

The ProxyCommand handler for .pod namespace routing. You don't call this directly. SSH invokes it automatically when connecting to a *.pod hostname, as configured by podspawn setup.

podspawn connect <user> <host> <port>

Arguments

ArgumentSSH tokenDescription
user%rSSH username
host%hHostname including .pod suffix
port%pSSH port

Resolution logic

  1. If the host does not end in .pod, connect directly (passthrough)
  2. localhost.pod always resolves to 127.0.0.1 without reading any config
  3. For other .pod hosts, load ~/.podspawn/config.yaml
  4. Look up the host in servers.mappings for a direct match
  5. Fall back to servers.default if no mapping exists
  6. Open a TCP connection (10-second timeout) and relay stdin/stdout bidirectionally

Resolution examples

Given this config:

servers:
  default: devbox.company.com
  mappings:
    gpu.pod: gpu-server.company.com
HostResolves to
gpu.podgpu-server.company.com:22
work.poddevbox.company.com:22 (default fallback)
localhost.pod127.0.0.1:22 (hardcoded)
myserver.commyserver.com:22 (passthrough, not .pod)

The connect command is a thin TCP relay. It does not implement any SSH protocol logic. The SSH handshake happens between your SSH client and the server's sshd.


update

Updates the podspawn binary to the latest release from GitHub.

podspawn update
podspawn update --check

Flags

FlagDefaultDescription
--checkfalseOnly check for updates, do not download or install

Examples

# Check without installing
$ podspawn update --check
current: v0.2.1, latest: v0.3.0

# Update in place
$ podspawn update
current: v0.2.1, latest: v0.3.0
downloading v0.3.0...
updated to v0.3.0

# Already up to date
$ podspawn update
already on latest version (v0.3.0)

How it works

  1. Fetches the latest release tag from the GitHub API (/repos/podspawn/podspawn/releases/latest)
  2. Compares against the current binary's version
  3. If --check is set, stops after printing the version comparison
  4. Downloads podspawn_<version>_<os>_<arch>.tar.gz from the release assets
  5. Extracts the binary to a temp directory
  6. Replaces the current binary with an atomic rename (falls back to copy for cross-device moves)

The update replaces the binary at its current path. If podspawn is installed in a system directory like /usr/local/bin, you may need sudo podspawn update.

On this page