tipod documentation
Isolated Linux VMs with SSH, a disk saved every minute, and a pause that never loses the disk. Install the CLI and get to a root shell in about five minutes.
Quickstart
Install, sign up with an invite, create a pod, work in it, then pause it without losing state.
1. Install the CLI
$ curl -fsSL https://tipod-control-plane.ramasamy-vignesh.workers.dev/install | sh
# open a new terminal, or: export PATH="$HOME/.tipod/bin:$PATH"
PS> irm https://tipod-control-plane.ramasamy-vignesh.workers.dev/install.ps1 | iex
The installer verifies the binary against the published sha256 manifest, installs to ~/.tipod/bin, and writes a default config only if none exists — it never overwrites an existing API key. Re-run it any time with pod upgrade.
2. Create an account
Signup is invite-gated during the friends beta. Ask the operator for the shared token, then:
$ pod signup --email you@example.com --invite <token>
$ pod whoami
Signup prints an API key once and grants trial machine-time. If you already have a key, run pod signin tp_… instead. pod setup runs the guided browser flow.
3. Create a pod
$ pod create --type small --name first-pod --ttl 900
✓ Created pod tp_abc123 · type small · state creating
$ pod ls
$ pod get active
active is an alias for the last pod you created in this shell session, so you rarely need to type a full ID. --ttl sets the auto-stop timer in seconds (default 3600, max 2592000); pass --no-auto-stop to disable it.
4. Work, pause, wake
$ pod shell active
$ pod pause active
# poll until state is archived
$ pod get active
$ pod wake active
$ pod rm active
Pause writes a verified snapshot before the pod is archived. The disk is saved about once a minute while the pod is running.
Friends beta
Control plane: https://tipod-control-plane.ramasamy-vignesh.workers.dev. Custom domains are not live yet, so the CLI and API both target the Worker URL directly.
Machines
Two predictable x86_64 shapes for the first release.
| Type | Resources | Rate | Good for |
|---|---|---|---|
small | 2 vCPU · 4 GB | 0.5× machine-time | Agent tasks, scripts, everyday shells |
default | 4 vCPU · 8 GB | 1× machine-time | Builds and longer sessions |
--type also applies when restoring or forking: pod wake --type small <id> resizes on restore, and pod clone --type default <id> sizes the fork. large is post-MVP.
Base image
Pods start from a baked Ubuntu x86_64 template with OpenSSH, qemu-guest-agent, Docker, Git, Node, Python, and common CLI tools.
Not included yet
GPUs, ARM images, Windows and macOS guests, memory (RAM) snapshots, and desktop streaming are outside the initial release. Dedicated public IPs are in procurement.
Pod states
Lifecycle operations are asynchronous. Poll with pod get <id> and read transitions with pod log <id>.
| State | Meaning |
|---|---|
creating / provisioning | VM clone in progress |
ready / idle / running | Running and reachable over SSH |
stopping / archiving | Pause requested; final snapshot in progress |
archived | Paused with a completed snapshot — resumable with wake |
stopped | Stopped before the archiving step |
error | Last operation failed; see pod log |
Abort-stop
If the final snapshot or the shutdown fails, the pause aborts: the pod reverts to its running state, billing continues, and a pod.stop_failed event is recorded. A pod reaches archived only when a verified snapshot exists, so a pause can never silently discard your disk.
CLI reference
One binary for humans and automation. Every command accepts --json, which is enabled automatically when output is piped.
Pod lifecycle
| Command | Does | Notable flags |
|---|---|---|
pod create | Create a pod | --type --name --ttl --no-auto-stop --env --no-env |
pod ls | List pods | --filter r|s|p|t|e --all |
pod get <id> | Show pod details | — |
pod pause <id> | Snapshot, archive, pause billing | — |
pod wake <id> | Resume from latest snapshot | --type --no-env |
pod clone <id> | Fork from latest snapshot | --type --env --no-env |
pod ttl <id> | Change the auto-stop timer | --hours --ttl --no-auto-stop |
pod rm <id> | Delete a pod | — |
pod log <id> | Read lifecycle events | --follow --limit |
pod cancel <id> | Interrupt the running command | — |
Access
| Command | Does | Notable flags |
|---|---|---|
pod shell <id> [cmd] | Open a shell or run a command | — |
pod cp | Copy files to or from a pod | --recursive |
pod tunnel <id> | Forward a port to localhost | --remote --local --bind |
pod expose <id> <port> | Publish a port on HTTPS | --title --public --private |
pod exposes <id> | List exposed ports | — |
pod unexpose <id> <port> | Remove an exposed port | — |
Snapshots, templates, account
| Command | Does |
|---|---|
pod snap ls [id] | List snapshots, all or for one pod |
pod snap latest <id> | Show the snapshot that wake and clone use |
pod tpl ls | List your templates |
pod tpl create <id> --name | Create a template from a paused pod |
pod tpl clone <tpl> --name | Create a pod from a template |
pod tpl rm <tpl> | Delete a template |
pod quota | Show balance, active pods, and rate limits |
pod key create|ls|rotate|revoke | Manage API keys |
pod whoami | Show account, plan, and config path |
pod open | Open the dashboard in a browser |
pod upgrade | Re-run the installer to upgrade |
pod autocomplete bash|zsh|fish | Print shell completions |
Config & flags
Configuration lives at ~/.config/tipod/config.json with restrictive permissions.
{
"apiUrl": "https://tipod-control-plane.ramasamy-vignesh.workers.dev/v1",
"apiKey": "tp_…"
}
| Global flag | Effect |
|---|---|
--api-url <url> | Override the endpoint (env: TIPOD_API_URL) |
--json | Machine-readable output; automatic when piped |
--no-update | Skip the startup version check |
Run pod path to print the resolved path and contents. The managed SSH key lives at ~/.ssh/tipod_ed25519.
SSH, files, tunnels
Pods are ordinary Linux machines; the CLI manages connection details for you.
$ pod shell tp_abc123
$ pod shell tp_abc123 "cd app && npm test"
$ pod shell tp_abc123 -- bash -s < ./setup.sh
$ pod cp ./build.zip tp_abc123:/home/user/
$ pod cp --recursive ./project tp_abc123:/home/user/
$ pod tunnel tp_abc123 --remote 5432 --local 5433
SSH becomes available once the pod is running and has an IP. Keys are injected with POST /pods/{id}/sshkey and stored locally at ~/.ssh/tipod_ed25519.
Expose a port
Publish a service on HTTPS without touching a firewall rule yourself.
$ pod expose tp_abc123 3000 --title "App preview"
✓ https://host-tp_a1b2c3.tipod.dev?_token=…
$ pod expose tp_abc123 3000 --public
$ pod exposes tp_abc123
$ pod unexpose tp_abc123 3000
Routes are private by default and carry an _token query parameter. The proxy strips the token before forwarding and only accepts upstream targets registered by the host orchestrator. Custom domains are not configured yet.
Templates
Bake an environment once, then fork it per user, task, or CI run.
$ pod pause tp_source
$ pod tpl create tp_source --name "Node baseline"
$ pod tpl clone tpl_abc123 --name customer-1 --no-env
$ pod tpl ls
A template needs a paused pod with a completed snapshot. To update one, wake the source, make changes, and pause it again — later forks use the newest snapshot. --no-env drops account secrets from the fork, and it is one-way.
Snapshots
Disk-level snapshots power wake, clone, and templates.
| Kind | When | Retention |
|---|---|---|
periodic | About every minute while running | 60 minutely · 24 hourly · 7 daily |
stop | On pause, before archiving | Kept as the resume point |
What a snapshot holds
- Included — user files, installed packages, configuration, enabled
systemdservices, and Docker named volumes. - Not included — running process memory, hostname, network identity, and SSH host keys.
Because RAM is not captured, processes you started by hand need restarting after wake. Services enabled through systemd come back on their own.
$ pod snap ls tp_abc123
$ pod snap latest tp_abc123
Billing & quota
Subscription machine-time with carry-forward. Paused pods are free.
| Item | Value |
|---|---|
| Starter | $20/month → 2,000,000 default-equivalent seconds per paid invoice |
small | 0.5× machine-time rate |
default | 1× machine-time rate (~$0.036/hour) |
| Paused or archived | $0.00 — the disk is still kept |
| Balance | Carries forward; it does not reset monthly |
The friends beta may grant trial seconds at signup. Check your balance with pod quota, or read GET /limits, GET /billing/usage, and GET /billing/transactions.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
orchestrator_unavailable | The control plane is up but no host agent is registered. Install the Go orchestrator on a KVM-capable host to enable VM operations. |
pod.stop_failed | Abort-stop triggered: the snapshot could not be verified, so the pod stayed running. Retry pod pause; if it repeats, check host disk space. |
| SSH connection refused | The pod has no IP yet. Wait for ready in pod get, then retry. |
quota_exceeded | Machine-time balance or the active-pod cap is exhausted. Check pod quota; pause idle pods. |
Process gone after wake | Expected — snapshots are disk-only. Run the process under systemd so it restarts automatically. |
pod: command not found | Open a new terminal, or add ~/.tipod/bin to PATH. |
FAQ
What exactly is a pod?
An isolated KVM/QEMU virtual machine running Ubuntu — a real computer for an agent or a person. Files and packages survive a clean pause and wake.
Is this a container?
No. Pods are full VMs with their own disk and systemd, managed by a host orchestrator. You get machine semantics rather than a process tree.
Why is the CLI verb pause but the HTTP route /stop?
The CLI uses short, human verbs; the underlying HTTP routes keep RESTful names. pause maps to POST /pods/{id}/stop, wake to POST /pods/{id}/resume, and clone to POST /pods/{id}/fork.
Am I billed while a pod is paused?
No. Machine-time accrues per second only while a pod runs. Paused and archived pods cost nothing and keep their disk.
Can two agents share secrets?
Account-level secrets are injected into new pods unless you pass --no-env. Isolation still holds between running VMs — one pod cannot read another's live filesystem.
Where do pods run?
APAC-first, with Singapore staging today. Dedicated public IPs are on the roadmap; for now SSH uses the host IP plus a mapped port.
Do you support GPUs?
Not in the initial scope.