Workflows

Common deploy and update flows for yeet.

These are the core workflows used day-to-day. All examples use placeholders.

Deploy a compose stack (most common)

yeet run <svc> ./compose.yml --net=lan

Get the LAN IP:

yeet info <svc>
yeet ip <svc>

After your first run, yeet writes a yeet.toml in the project directory. You can then re-run with just the service:

yeet run <svc>

For a guided new-service deploy, run yeet run --web from the project directory. The web flow keeps the browser form alive until a deploy succeeds. Runtime errors appear in the browser terminal and the local terminal, and you can edit the form and retry without restarting yeet run --web.

Update later (compose services; update pulls images and recreates containers):

yeet run <svc> ./compose.yml          # redeploy config, reuse existing images
yeet run --pull <svc> ./compose.yml   # pull latest + redeploy
yeet run --force <svc> ./compose.yml  # redeploy even if no changes are detected
yeet docker outdated       # check for image updates without changing containers
yeet docker pull <svc>     # prefetch images without restarting
yeet docker update <svc...> # pull + recreate containers (restart)
yeet docker update --outdated # update compose services that have image updates

Set or move a service root

Use --service-root to choose where a new service lives on the catch host:

yeet run vaultwarden ./compose.yml --service-root=/srv/apps/vaultwarden
yeet run vaultwarden ./compose.yml --service-root=tank/apps/vaultwarden --zfs

The path is absolute on the catch host. The parent directory (/srv/apps in this example) must already exist; yeet can create the final service directory. Each service root contains bin, run, env, and data.

For ZFS-backed service roots, pass --zfs and use the dataset name as --service-root; catch resolves the dataset mountpoint and stores both the dataset identity and the resolved filesystem path.

If the dataset does not exist, catch runs plain zfs create <dataset>. Parent datasets must already exist. If the dataset already exists or its mountpoint already contains files, catch prints a warning and deploys into it.

yeet run can set the initial root, but it cannot move an existing service. To move a service, stop it first, then use yeet service set:

yeet stop vaultwarden
yeet service set vaultwarden --service-root=/mnt/fast/vaultwarden --copy
yeet service set vaultwarden --service-root=tank/apps/vaultwarden --zfs --copy

Use --copy to copy the old root into the new root, or --empty to create a new empty root:

yeet service set vaultwarden --service-root=/mnt/fast/vaultwarden --empty
yeet service set vaultwarden --service-root=tank/apps/vaultwarden --zfs --empty

yeet service set leaves the old root in place. Non-interactive migrations must pass either --copy or --empty. For the migration examples above, /mnt/fast must already exist.

If the migration command was run outside the directory that contains the service's yeet.toml, update the local replay config afterward:

yeet service sync vaultwarden --config ~/yeet-services/yeet.toml

For a config file with several services on the selected host:

yeet service sync --all --config ~/yeet-services/yeet.toml

Configure ZFS snapshots

ZFS-backed service roots get yeet-managed snapshots before redeploys, Docker image updates, and ZFS-backed service-root migrations. First deploys are skipped. The built-in default is enabled and required, keeps 5 yeet-created snapshots, and prunes yeet-created snapshots older than 7 days.

Show or change catch-wide defaults:

yeet snapshots defaults show
yeet snapshots defaults set --enabled=false
yeet snapshots defaults set --enabled=true --keep-last=5 --max-age=7d
yeet snapshots defaults set --events=run,docker-update --required=false

Override one service:

yeet service set vaultwarden --snapshots=off
yeet service set vaultwarden --snapshots=on --snapshot-keep-last=3 --snapshot-max-age=72h
yeet service set vaultwarden --snapshot-events=run,docker-update
yeet service set vaultwarden --snapshots=inherit

Snapshot-only yeet service set commands do not require the service to be stopped. Service-root moves still require the service to be stopped. The catch DB is the live source of truth; run yeet service sync <svc> to mirror per-service snapshot overrides back into yeet.toml.

Build from a Dockerfile and run

yeet run <svc> ./Dockerfile

Deploy a binary

GOOS=linux GOARCH=amd64 go build -o ./bin/<svc> ./cmd/<svc>
yeet run <svc> ./bin/<svc>

Run a registry image or push a local image (less common)

Pull a public image on the catch host:

yeet run <svc> nginx:latest

Push a local image instead (for custom builds):

yeet docker push <svc> <local-image>:<tag> --run

Update a service

For binaries or compose files, just re-run yeet run with the new payload. For compose images, add --pull (or use yeet docker update <svc...>) when you want to refresh selected services. Use svc@host for individual targets that should run on a specific host, such as yeet docker update web api@catch-b worker. Use yeet docker update --outdated to batch-refresh only compose services reported by yeet docker outdated; scan errors are printed as skipped rows so they are not mistaken for clean no-update results.

When a service already has a yeet.toml entry, a payload-only redeploy reuses the saved run options and changes only the payload. You do not need to restate the original network, tags, service root, snapshot, or payload args.

yeet run <svc> ./bin/<svc>

Stage then commit

yeet stage <svc> ./bin/<svc>
yeet stage <svc> show
yeet stage <svc> commit

Edit config or env

yeet edit <svc>          # compose or systemd units
yeet env edit <svc>       # env file
yeet env copy|cp <svc> ./app.env
yeet env set <svc> PORT=8080 LOG_LEVEL=debug
yeet env set <svc> LOG_LEVEL=     # unset

Copy files into a service

yeet copy ./app.env <svc>:
yeet copy ./config.yml <svc>:config/config.yml
yeet copy ./configs/ <svc>:config/

Run a cron job

yeet cron <svc> ./job.sh "0 9 * * *" -- --job-arg foo

Tail logs

yeet logs -f <svc>

Multi-host usage

Here, <host> refers to the catch host (Tailscale/tsnet hostname), not the SSH machine host. See Tailscale.

Set the host per command:

CATCH_HOST=<host> yeet status

Or use @host:

yeet status@<host>
yeet run <svc>@<host> ./compose.yml

Or save it:

yeet prefs --host=<host> --save