Skip to main content

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.

Repositories (repos) are named collections of VM image tags, scoped to your organization. A tag inside a repository is a named pointer to a specific commit (e.g. my-app:latest or my-app:v1.2). Repos can be private (the default, visible only to your org) or public (discoverable and forkable by any Vers user). If you’re already comfortable with OCI registries like Docker Hub, the mental model maps over directly. A repo is the thing people pull from; a tag is the moving pointer; a commit is the immutable snapshot the tag currently names. The difference is what’s inside: a Vers commit is a full VM snapshot (filesystem and memory), not a layered container image.

Why Repos Exist

Repos solve two different problems. It helps to keep them separate in your head, because you’ll likely use both.

1. Sharing images across accounts

Commits are private to the org that created them. There’s no way to hand someone a commit ID from another org and have them restore it. Repos are the mechanism for cross-account distribution: mark a repo public, tag the commits you want to share, and anyone with a Vers account can fork them into their own org. This is how Vers publishes supported images, pre-baked VMs for common use cases that the Vers team maintains and refreshes. The currently supported set:
RepositoryWhat’s in it
node:latestNode.js LTS with npm, git, curl, nginx, iproute2
python:latestPython 3 with pip, venv, git, curl, nginx, iproute2
rust:latestRust stable with cargo, git, curl, nginx, iproute2
go:latestGo 1.22 with git, curl, nginx, iproute2
pi-agent:latestpi coding agent pre-installed with tmux/FIFO scaffold, ready for remote driving
cc-agent:latestClaude Code pre-installed with the runtime environment it needs
You can fork any of them with one command and get a running VM plus a copy of the commit in your own repo, ready to customize further.
Supported images are rebaked on a regular cadence so that <name>:latest stays current with the upstream toolchain. If you need to pin a specific build, capture the commit ID at fork time (vers repo fork output includes it) and use that commit ID directly later.

2. Organizing your own commits

The other use case is purely private: a place to collect commits you care about, under names you can remember. Instead of saving commit IDs in a scratchpad, you tag them:
vers repo create my-app
vers repo tag create my-app latest c7e4a2f1…
vers repo tag create my-app known-good c7e4a2f1…
vers repo tag create my-app pre-migration 9b8d3c10…
From then on, the CLI (vers run-commit <repo>:<tag> --ref) and the API ({"ref": "my-app:latest"}) both accept repo:tag references anywhere you’d use a commit ID, which makes your workflows much easier to read and rerun. A common pattern: your CI builds a fresh VM image for your app on every merge to main, points my-app:latest at the new commit, and the team pulls a pre-baked VM matching trunk in seconds instead of spending minutes on apt-get and migrations. See CI: maintaining a latest image for the full recipe.

The Object Model

Organization (your account)

├── Repository "my-app"              (private or public)
│   ├── Tag "latest"      → commit c7e4…
│   ├── Tag "v1.0"        → commit 1a2b…
│   └── Tag "staging"     → commit 88ff…

├── Repository "internal-tools"      (private)
│   └── Tag "latest"      → commit 4d5e…

└── Repository "public-demo"         (public, forkable by others)
    └── Tag "latest"      → commit 9b8d…
ConceptWhat it isScope
RepositoryA named container for tags. Has a visibility flag.Per-org
TagA movable pointer from a name to a single commit.Per-repo
CommitImmutable snapshot of a VM’s full state.Per-org (referenceable across orgs only via public repos + fork)
ReferenceThe repo_name:tag_name string (and org_name/repo_name:tag_name for cross-org).n/a
A few properties worth internalizing:
  • Tags are movable. Moving a tag is a cheap pointer update; the commit it used to point to still exists as long as some VM or other tag still references it.
  • Deleting a repo or tag does not delete commits. Commit garbage collection is a separate concern; removing the label doesn’t destroy the artifact.
  • Repo names are per-org. Two different orgs can both have a my-app repo; they’re disambiguated by org namespace (acme/my-app vs globex/my-app) for public lookups.
  • Names are validated. Repo names: 1-64 characters, alphanumeric plus hyphens, underscores, and dots. Tag names: 1-128 characters, same character set.
  • Tagging requires org ownership of the commit. You can only tag commits your org owns. To tag a commit from somebody else’s public repo, fork it first, forking is what actually copies the commit into your org.

References: repo:tag and org/repo:tag

There are two reference shapes:
FormWhen to useExample
repo:tagOperations scoped to your own orgmy-app:latest
org/repo:tagCross-org ops (currently only vers repo fork)vers/python:latest

Where refs are accepted

Via the CLI, pass --ref:
vers run-commit my-app:latest --ref --vm-alias dev
Via the API (POST /api/v1/vm/from_commit), send the ref field instead of commit_id:
// Before
{ "commit_id": "c7e4a2f1-…" }

// After
{ "ref": "my-app:latest" }
There’s also POST /api/v1/vm/branch/by_ref/{repo_name}/{tag_name} when you want to branch from a ref rather than restore via from_commit.
--ref is specifically required on the CLI because the positional argument is otherwise sent as {"commit_id": "..."}. Without --ref, a repo:tag-shaped argument is rejected up front with a hint, since sending it as commit_id would fail UUID parsing on the server.
Cross-org org/repo:tag references are only consumed by vers repo fork and the public-repository API. You can’t from_commit/run-commit --ref directly against another org’s ref. The expected path is always fork-then-use.

Visibility and Publishing

Every repo has an is_public flag, false by default. You flip it with:
vers repo visibility my-app --public          # publish
vers repo visibility my-app --public=false    # unpublish
When a repo is public:
  • Any authenticated Vers user can discover it via GET /api/v1/public/repositories
  • Any user can read its tags and fork it into their own org
  • Your org name becomes part of its public identity: your-org/my-app:latest
Private repos are only visible to members of your own org. There’s no in-between “share with these accounts” state right now; it’s public or private.
Publishing a repo exposes the commits its tags point to. The commits themselves don’t become public objects in isolation (they’re only fetchable via the public repo surface), but they are fetchable, so treat them like you’d treat a public Docker image. Don’t tag commits that contain secrets in /etc/environment, ~/.netrc, private keys, etc.

Forking

A fork takes a public repo’s tag in another org and produces, in your org:
  1. A new running VM, branched from the source commit (via a copy-on-write clone, so this is fast; it does not boot and re-image a fresh VM)
  2. A new commit, captured from that VM, owned by your org
  3. A repository (either a new one, or an existing same-named one; see below)
  4. A tag pointing at the new commit
vers repo fork vers/pi-agent:latest
# ✓ Forked → pi-agent:latest
#   VM:     vm-7a1c…
#   Commit: c9e4…
You can override the target repo and tag names:
vers repo fork vers/pi-agent:latest --repo-name my-agent-base --tag-name v0
The result is fully independent. No upstream relationship is tracked. Modify the VM, re-commit, move tags, delete the source locally, whatever. Nothing links back. To pull fresh upstream changes later, run vers repo fork again.

Fork and existing repos

If a repo of the target name already exists in your org, the fork reuses it and adds the new tag to it. You only get an error in a race where two simultaneous forks both try to insert the same repo name. What isn’t handled automatically: if the target repo already has a tag with the target tag name, the tag insert will fail. Pass --tag-name to pick a different name, or delete the existing tag first with vers repo tag delete.

Typical Workflows

Personal organization (just tag your commits)

vers run --vm-alias dev
vers connect
# … set up a project, install deps …
exit

vers commit
# → commit c7e4…

vers repo create my-app
vers repo tag create my-app latest c7e4…

# Weeks later, on any machine:
vers run-commit my-app:latest --ref --vm-alias dev

Team CI: always-current dev environment

  1. Developer creates my-app repo once, makes it public if it should be forkable by contractors, private otherwise.
  2. CI action on every merge to main:
    • Forks the previous my-app:latest into a scratch VM (or branches from it)
    • Runs git pull, reinstalls deps, runs migrations, warms caches
    • Commits the VM
    • Points my-app:latest at the new commit with vers repo tag update my-app latest --commit <id>
  3. Team members pull the current build with vers run-commit my-app:latest --ref (or the equivalent API call) and land on a pre-baked dev VM matching trunk.
See CI: maintaining a latest image for a full walkthrough with example scripts.

Consuming a supported image

# Fork gives you a running VM, a commit, and a tagged repo in your org
vers repo fork vers/python:latest --repo-name py-base --tag-name latest
# ✓ Forked → py-base:latest
#   VM:     vm-2b4a…
#   Commit: f1a7…

# Connect to the VM the fork created
vers connect vm-2b4a…

# Or spin up a fresh VM from the fork's commit later
vers run-commit f1a7… --vm-alias my-py-env
See Forking a supported image for the full walkthrough.

Release tagging

# After a successful release build
vers repo tag create my-app v1.4.2 c9e4…
vers repo tag update my-app latest --commit c9e4…

# Roll back latest in a pinch
vers repo tag update my-app latest --commit <previous-commit>

CLI Cheat Sheet

CommandPurpose
vers repo createCreate a new repository
vers repo listList your org’s repositories
vers repo getShow details of a repo
vers repo deleteDelete one or more repos (keeps commits)
vers repo visibilityToggle public / private
vers repo forkFork a public repo into your org
vers repo tag createCreate a tag pointing at a commit
vers repo tag listList tags in a repo
vers repo tag getShow tag details
vers repo tag updateMove a tag to a new commit, or edit description
vers repo tag deleteDelete tags (keeps commits)

API Surface

All endpoints require Authorization: Bearer <token> unless marked public.

Your org’s repositories

MethodPathPurpose
GET/api/v1/repositoriesList your org’s repos
POST/api/v1/repositoriesCreate a repo
GET/api/v1/repositories/{repo_name}Get one
DELETE/api/v1/repositories/{repo_name}Delete (tags removed, commits preserved)
PATCH/api/v1/repositories/{repo_name}/visibilitySet is_public
POST/api/v1/repositories/forkFork a public repo from another org

Tags inside a repo

MethodPathPurpose
GET/api/v1/repositories/{repo_name}/tagsList tags
POST/api/v1/repositories/{repo_name}/tagsCreate a tag
GET/api/v1/repositories/{repo_name}/tags/{tag_name}Get a tag
PATCH/api/v1/repositories/{repo_name}/tags/{tag_name}Move tag and/or edit description
DELETE/api/v1/repositories/{repo_name}/tags/{tag_name}Delete tag

Public discovery (readable by any authenticated user)

MethodPathPurpose
GET/api/v1/public/repositoriesList all public repos across all orgs
GET/api/v1/public/repositories/{org}/{repo}Get one by org + name
GET/api/v1/public/repositories/{org}/{repo}/tagsList tags
GET/api/v1/public/repositories/{org}/{repo}/tags/{tag}Get a tag

Using references when restoring VMs

POST /api/v1/vm/from_commit accepts three alternative body shapes:
// 1. By commit ID (original form, always supported)
{ "commit_id": "c7e4a2f1-…-…" }

// 2. By legacy org-scoped tag (pre-repos commit_tags)
{ "tag_name": "my-old-tag" }

// 3. By repo:tag reference (new, recommended path for repos)
{ "ref": "my-app:latest" }
There’s also POST /api/v1/vm/branch/by_ref/{repo_name}/{tag_name} when you want to branch from a ref instead of restoring via from_commit.

Behavior Notes and Gotchas

  • Deleting a repo keeps its commits. The repo row and its tags are removed (tags cascade via FK). Commits remain reachable through any surviving tag, legacy commit_tags entry, or VM that still references them.
  • Deleting a tag keeps the commit. Same reason.
  • Tag moves are atomic pointer updates. No VMs are touched by moving my-app:latest from one commit to another; it’s a metadata-only change.
  • Fork creates a running VM and a commit. The branching step uses a Ceph CoW clone so it’s fast (seconds, not minutes), even for large images. The step that takes real time is the post-branch commit.
  • Fork is a copy, not a reference. No upstream relationship is tracked after completion. If the source repo updates, you won’t see it unless you fork again.
  • Fork into existing repo reuses it. Same-name target repo? The fork adds tags to it rather than erroring. A pre-existing tag with the same target tag name will fail the tag insert. Use --tag-name or delete the existing tag first.
  • Repo names are unique per-org, not globally. Public references always include the org name (acme/my-app).
  • Refs are case-sensitive and exact-match. No @latest parsing, no partial matches.
  • Tagging requires org ownership of the commit. You cannot create a tag in your repo that points to a commit another org owns, even if it’s visible via a public repo. The way you “import” someone else’s commit is vers repo fork; the fork re-commits the VM under your org.