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.
A golden image is a committed VM snapshot with your tools, dependencies, and configuration pre-installed. Boot from it to get a working environment in seconds instead of spending minutes on setup every time.
When You Need a Custom Golden Image
Use the stock default rootfs when your project has simple needs (standard Ubuntu packages, small dependency trees). Build a custom golden when:
- Installation takes too long:
npm install, pip install, or cargo build takes more than a few minutes on a fresh VM
- System dependencies are required: Your project needs
apt install packages not in the base Ubuntu image
- Version pinning matters: You need exact versions of Node, Python, Rust, or system libraries
- Build toolchains are heavy: Custom compilers, cross-compilation tools, CUDA/ROCm
- Caches improve performance: Pre-populated package manager caches, pre-built indexes, database dumps
- Agent tooling is needed: pi, Claude Code, or other agents pre-installed with the RPC scaffold ready
The Build Process
Step 1: Start a Builder VM
Use generous resources for the build. You’ll commit a lean snapshot at the end.
mkdir golden-build && cd golden-build
vers init
Edit vers.toml for build resources:
[machine]
mem_size_mib = 8192 # 8 GB for builds
vcpu_count = 4
fs_size_vm_mib = 30720 # 30 GB to fit toolchains + caches
[rootfs]
name = "default"
[kernel]
name = "default.bin"
vers run -N golden-builder
vers connect golden-builder
Step 2: Install Everything
Inside the VM, set up DNS and install your stack:
echo 'nameserver 8.8.8.8' > /etc/resolv.conf
apt-get update && apt-get install -y \
build-essential git curl wget tmux \
ca-certificates gnupg
Example: Node.js Agent Environment
# Node.js 22
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs
# Agent tooling
npm install -g @mariozechner/pi-coding-agent
npm install -g @anthropic-ai/claude-code
# Project dependencies (clone and install, then remove source)
git clone https://github.com/your-org/your-project /tmp/project-deps
cd /tmp/project-deps && npm install
# Keep node_modules warm in npm cache, remove source
npm cache add .
cd / && rm -rf /tmp/project-deps
Example: Python ML Environment
apt-get install -y python3 python3-pip python3-venv
python3 -m pip install --upgrade pip
python3 -m pip install torch transformers fastapi uvicorn
Example: Haskell Build Environment
# Install nix (single-user, no daemon)
groupadd -f nixbld
for i in $(seq 1 10); do
useradd -g nixbld -G nixbld -d /var/empty -s /sbin/nologin nixbld$i 2>/dev/null || true
done
mkdir -p /nix
curl -L https://nixos.org/nix/install | bash -s -- --no-daemon
mkdir -p /root/.config/nix
echo "experimental-features = nix-command flakes" > /root/.config/nix/nix.conf
echo ". /root/.nix-profile/etc/profile.d/nix.sh" >> /root/.bashrc
Step 3: Set Up Agent RPC Scaffold (Optional)
If the golden image will run coding agents, set up the FIFO infrastructure and systemd auto-start:
# Create the startup script
mkdir -p /root/bin
cat > /root/bin/start-pi-rpc.sh << 'EOF'
#!/bin/bash
set -e
mkdir -p /tmp/pi-rpc
[ -p /tmp/pi-rpc/in ] || mkfifo /tmp/pi-rpc/in
touch /tmp/pi-rpc/out /tmp/pi-rpc/err
if ! tmux has-session -t pi-keeper 2>/dev/null; then
tmux new-session -d -s pi-keeper "sleep infinity > /tmp/pi-rpc/in"
fi
if ! tmux has-session -t pi-rpc 2>/dev/null; then
tmux new-session -d -s pi-rpc "pi --mode rpc < /tmp/pi-rpc/in >> /tmp/pi-rpc/out 2>> /tmp/pi-rpc/err"
fi
echo "Pi RPC scaffold ready"
EOF
chmod +x /root/bin/start-pi-rpc.sh
Create systemd units for auto-start on VM restore:
cat > /etc/systemd/system/pi-rpc.service << 'EOF'
[Unit]
Description=Pi Coding Agent RPC Infrastructure
After=network.target
[Service]
Type=oneshot
User=root
ExecStart=/root/bin/start-pi-rpc.sh
EnvironmentFile=-/etc/environment
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/pi-rpc.path << 'EOF'
[Unit]
Description=Start pi-rpc when environment is written
[Path]
PathModified=/etc/environment
Unit=pi-rpc.service
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable pi-rpc.path
systemctl enable pi-rpc.service
systemctl start pi-rpc.path
Why .path units? Vers snapshots preserve memory state, not boot state. When a VM is restored from a commit, systemd doesn’t re-run start jobs. The .path unit watches for /etc/environment being written (which Vers does on every VM create), triggering service startup with fresh environment variables.The .path unit must be active (not just enabled) at commit time, or it won’t fire on restore.
Step 4: Hygiene Pass
Before committing, clean secrets and regenerate identity:
# Stop any running agent sessions
tmux kill-session -t pi-rpc 2>/dev/null || true
tmux kill-session -t pi-keeper 2>/dev/null || true
# Clear history and logs
rm -f /root/.bash_history /root/.zsh_history
> /etc/environment
journalctl --rotate 2>/dev/null || true
journalctl --vacuum-time=1s 2>/dev/null || true
rm -rf /tmp/* /var/tmp/*
# CRITICAL: Regenerate SSH host keys
# Without this, every VM branched from this snapshot shares the same SSH identity
rm -f /etc/ssh/ssh_host_*
ssh-keygen -A
# Clean caches and logs
npm cache clean --force 2>/dev/null || true
rm -rf /root/.npm/_logs 2>/dev/null || true
# Trim filesystem
fstrim -av 2>/dev/null || true
# Secret scan (excluding SSH host keys)
HITS=$(grep -rEao \
--exclude-dir=.git --exclude-dir=node_modules \
"gh[pousr]_[A-Za-z0-9]{20,}|sk-ant-[A-Za-z0-9_-]{20,}|AKIA[A-Z0-9]{16}" \
/etc /root /home 2>/dev/null | grep -v "/etc/ssh/" || true)
if [[ -n "$HITS" ]]; then
echo "WARNING: Potential secrets detected:"
echo "$HITS" | head -5
echo "Remove these before committing!"
else
echo "Hygiene scan clean"
fi
Vers snapshots capture everything: RAM, process state, disk, logs. Any secret present at commit time is baked into the image and available to anyone who boots from it. Always stop services and clear /etc/environment before committing.
Exit the VM:
Step 5: Commit and Tag
vers commit golden-builder
# Output: Commit ID: c1a2b3c4...
Save the commit ID. You can now boot new VMs from this snapshot:
vers run-commit c1a2b3c4... -N my-agent-1
Step 6: Clean Up the Builder
The builder VM used generous resources. Delete it once you have the commit:
vers delete -y golden-builder
Using Golden Images
Boot from a Commit ID
vers run-commit <commit-id> -N worker-1
Share with Team Members
Share the commit ID directly. Anyone with Vers access can boot from it:
# Team member runs:
vers run-commit c1a2b3c4... -N my-env
Updating Golden Images
When dependencies change, build a new golden image:
- Boot a fresh VM from the old golden image
- Update what changed (
npm update, apt upgrade, etc.)
- Re-run the hygiene pass
- Commit as a new snapshot
Keep a record of commit IDs and what they contain. A simple convention:
# golden-images.md
| Date | Commit ID | Contents |
|------------|------------------|-----------------------------------|
| 2026-04-19 | c1a2b3c4... | Node 22, pi, Claude Code, RPC |
| 2026-04-15 | d5e6f7g8... | Node 22, pi only |
Key Principles
Stop services before commit. Running services hold environment variables in memory. When someone boots from the snapshot, the service resumes with the old env vars, ignoring the new VM’s /etc/environment. Always stop, commit, and let systemd .path units restart with fresh env.
Regenerate SSH host keys. Without this, every VM from the snapshot has the same SSH identity. Security risk.
Clear /etc/environment. Vers writes environment variables here on VM create. If the file already has content at commit time, the .path unit may not detect a change (same content = no mtime change = no trigger).
Size the builder generously, the image inherits only what’s on disk. A 30 GB builder VM with 8 GB RAM can produce a golden image that runs fine on 2 GB RAM and 10 GB disk, as long as the installed software fits.
See Also