Run services and VMs on your own Linux hosts from your workstation.
yeetrun.com · Quick Start · Install · Docs
Yeet is a CLI for deploying and operating services on Linux hosts you control.
You run yeet locally. yeet init installs the catch daemon on a host over
SSH. After setup, yeet talks to catch through Tailscale.
Use yeet for:
- Docker Compose stacks and container images.
- Local Dockerfiles and locally built images.
- Linux binaries and scripts.
- Cron jobs.
- Linux VMs on KVM-capable hosts.
Yeet fits single-operator homelabs and small private infrastructure. It expects Linux hosts with systemd. Services currently run as root-owned systemd units on the catch host.
This path installs yeet locally, bootstraps one host, creates a service workspace, and runs a disposable container.
Run this on your workstation:
curl -fsSL https://yeetrun.com/install.sh | shTo install the nightly build instead:
curl -fsSL https://yeetrun.com/install.sh | sh -s -- --nightlyConfirm the CLI is available:
yeet --helpDo this before running yeet init. Catch must join your tailnet as a tagged
device, usually tag:catch. User-owned catch nodes are rejected.
Your tailnet policy must also allow the setup user to reach catch on TCP port
41548 with the yeetrun.com/app/yeet app permissions read, manage, and
ssh. First setup requires all three; split them into narrower roles later if
you need to.
In the Tailscale admin console, open Trust credentials -> Credential ->
OAuth, then create an OAuth client secret.
Choose one setup:
- Simple setup: grant
All - Read & Writeif you are comfortable giving the credential broad Tailscale API access. - Least-privilege setup: grant Auth Keys write (
auth_keys) and select the tag the credential may assign. Usetag:catchfor catch-only installs. Use an owner tag such astag:yeetif you plan to create service Tailscale nodes later with--net=ts.
Keep the tskey-client-... secret ready. Interactive yeet init asks for it
during first setup.
See Tailscale Setup for the minimal policy snippet, and Tailscale Access Grants for the permission model.
Run this from your workstation:
yeet init root@<machine-host><machine-host> is the SSH target. If you use a non-root SSH user, yeet runs
the remote install with sudo.
During first setup, paste the Tailscale OAuth client secret when prompted. For repeatable setup, pass it explicitly:
yeet init --ts-client-secret=<secret> root@<machine-host>If Docker is missing on a Debian/Ubuntu-style host, init can install it:
yeet init --install-docker --ts-client-secret=<secret> root@<machine-host>For VM payloads on a host that exposes KVM and TUN/TAP, install the VM tools too:
yeet init --install-docker --install-vm-tools --ts-client-secret=<secret> root@<machine-host>If the host can run VMs and does not already have a LAN bridge, interactive
yeet init can also ask to prepare br0 for VM --net=lan networking. You
can skip that prompt and let the first yeet run <vm> ... --net=lan ask before
it creates the VM service.
Skip VM tools for the first run unless you already know the host supports VMs. Containers, binaries, scripts, and cron jobs work without VM support.
After yeet init, normal commands target the catch hostname, not the SSH
machine host.
yeet version
yeet statusIf yeet did not save this host as the default, pass the catch hostname:
yeet --host=<catch-host> statusDo this before your first yeet run. A successful deploy writes yeet.toml in
the current directory.
mkdir -p ~/yeet-services
cd ~/yeet-servicesUse this directory for real homelab services too. See the Service Workspace guide before deploying third-party Compose apps, env files, Dockerfiles, scripts, or binaries.
Start with a small container:
yeet run -p 18080:80 hello nginx:alpine
yeet status hello
yeet logs helloCheck the published port from the catch host:
yeet ssh -- curl -fsS http://127.0.0.1:18080/ >/dev/nullRemove the service and its data:
yeet rm --clean helloRead the confirmation prompt before accepting. --clean deletes the service
data, including VM disks for VM services, and removes the disposable
yeet.toml entry.
Run deploy commands from your service workspace. Yeet writes yeet.toml in the
current directory after a successful deploy.
Use the guided deploy form when you do not want to remember flags:
yeet run --web
yeet run --web <svc>
yeet run --web <svc> ./compose.ymlDeploy common payloads:
yeet run <svc> ./compose.yml
yeet run -p 8080:80 <svc> nginx:alpine
yeet run <svc> ./Dockerfile
yeet docker push <svc> <local-image>:<tag> --runService names created by yeet run must use lowercase letters, numbers, and
dashes, start with a letter, and end with a letter or number.
Deploy a binary, script, or cron job:
GOOS=linux GOARCH=amd64 go build -o ./bin/<svc> ./cmd/<svc>
yeet run <svc> ./bin/<svc>
yeet run <svc> ./script.sh -- --app-flag value
yeet cron <svc> ./job.sh "0 9 * * *"Create a VM on a KVM-capable catch host:
yeet vm images catalog
yeet run <vm> vm://ubuntu/26.04
yeet ssh <vm>After the first successful deploy, yeet writes service config to yeet.toml.
From that directory, redeploy the saved service with:
yeet run <svc>Check status and logs:
yeet status
yeet status <svc>
yeet info <svc>
yeet logs -f <svc>Open a shell or run a remote command:
yeet ssh
yeet ssh <svc>
yeet ssh -- uname -a
yeet ssh <svc> -- ls -laAfter yeet init, host and regular service shells use catch over Tailscale, so
they do not require host SSH keys or a host password. VM services still connect
to the guest operating system with SSH.
Control or remove a service:
yeet restart <svc>
yeet stop <svc>
yeet start <svc>
yeet rm <svc>yeet rm <svc> keeps service data by default and prompts before removing the
local config entry. Add --clean when you want yeet to delete service data and
remove the local yeet.toml entry too.
Use root@<machine-host> only for yeet init. Use the catch hostname for
normal commands.
CATCH_HOST=<catch-host> yeet status
yeet --host=<catch-host> status
yeet status@<catch-host>
yeet run <svc>@<catch-host> ./compose.ymlSave a default catch host:
yeet prefs --host=<catch-host> --saveCheck the local CLI and catch hosts:
yeet upgrade checkUpgrade from verified GitHub release assets:
yeet upgradeWhen you run from a service workspace with yeet.toml, yeet upgrade includes
all project catch hosts plus the default catch host. Use --host=<catch-host>
only when you want to upgrade one catch host.
yeet upgrade --host=<catch-host>To reinstall a release even when a component already looks current, newer, or locally built:
yeet upgrade --forceTo install a specific public release:
yeet upgrade --version v0.6.1 --forceStart with the quick path before adding optional features.
- Docker is required for container payloads.
- VMs require x86_64 Linux, KVM at
/dev/kvm, TUN/TAP, and VM filesystem tools on the catch host. --net=svccreates a private service network, adds yeet DNS, and sends ordinary outbound internet through the catch host.--net=svc,tskeepssvcbehavior and gives the service its own Tailscale identity. Use this for most Tailscale-exposed services.--net=lanrequests a LAN or VLAN address. Outbound internet comes from the DHCP gateway on that network.- VM
--net=lanattaches the guest TAP to a host bridge. On supported Debian/Ubuntu hosts, yeet can preparebr0duringyeet initor before the first VM LAN create. - Plain
--net=tsis tailnet-only unless you configure a Tailscale exit node. - ZFS is optional and enables dataset-backed service roots, snapshots, and fast repeated VM disk clones.
Use the manual before enabling optional storage or networking:
- Quick Start
- Installation
- Workflows
- Payloads
- yeet command reference
- catch reference
- Troubleshooting
- FAQ
Use mise to install the repo toolchain:
mise installBuild and test:
mise exec -- go build ./cmd/yeet
mise exec -- go build ./cmd/catch
mise exec -- go test ./...Install local hooks before contributor work:
mise run install-githooksRun the normal quality gate before publishing changes:
mise run qualityYeet is for hosts you control. It is not a multi-tenant platform. Services managed by catch currently run as root-owned systemd units.
BSD 3-Clause. See LICENSE.