Standalone (no-registry) deploys
Ship images directly to hosts over SSH with no registry โ layer-level dedup via the bundled unregistry transport.
Build and ship a service in one yoink up --build: no CI, no registry. Default transport is unregistry (layer-level dedup over SSH); tarball fallback handles air-gapped hosts. For the build-origin ร distribution mental model, see Deploy modes.
The minimum config
Drop a yoink.yaml next to your Dockerfile:
hosts:
- { address: my-server, user: deploy } # tailnet hostname
services:
- name: my-tool
image: my-tool # bare name โ no registry prefix
tag: dev
build:
context: . # `.` = same directory as yoink.yaml
run:
port: 8080yoink up --build # build + ship + run, in one commandEdit, rerun, the new version rolls. Skips the GitHub Actions + container-registry setup when you don't need it.
For separate build and deploy steps (sharing the artifact across shells, or building on a beefy laptop and deploying from a thin runner), use the explicit two-command form:
yoink build my-tool # docker build โ tag local image as my-tool:dev
yoink up --service my-tool # save+load to each hostHow yoink chooses
A service with build: is shipped from the operator's local docker daemon over SSH. A service without build: is pulled by each host from the registry in its image: ref. Per-service, no flag.
The local-shipping path uses the unregistry transport by default; --transport tarball switches to the whole-image stream.
--no-registry forces local-shipping for every service (offline/airgapped deploys, or shipping a locally-modified version of a public image), skipping registry pulls entirely.
How the unregistry transport works (default)
For each host:
- yoink starts an ephemeral
ghcr.io/psviderski/unregistrysidecar. Unregistry is an OCI registry that reads/writes the host's image store via the containerd socket with no separate blob storage; images land indocker imagesimmediately on push. - yoink opens an SSH-tunnelled local port to the sidecar.
- yoink reads the image bytes from the operator's docker daemon and pushes blob-by-blob over the SSH tunnel:
HEADfirst,PUTonly if missing. Layer-level dedup: redeploys ship only the changed layers. - The sidecar is
--rmand force-removed by name on the nextupvia a label sweep.
The push runs from the operator process, not via docker push on the operator's daemon. On macOS Docker Desktop (and Rancher Desktop / Colima) the daemon lives in a Linux VM, so a docker push 127.0.0.1:<port>/... from the daemon would hit the VM's loopback rather than the operator's. Pushing from the operator process avoids any insecure-registries config.
Multi-host fan-out is concurrent across services: deploy time is max(per-host), not sum(per-host).
โ api:dev โ host-1 done ยท unregistry
โ api:dev โ host-2 done ยท unregistry
โ web:dev โ host-1 done ยท unregistryIf the unregistry setup fails (host can't pull the image, ssh forward refused), --transport=auto (the default) falls back to tarball with one warning line. --transport=unregistry turns those into hard errors.
Tarball transport (opt-out)
yoink up --build --transport tarballFor each (service, host), streams docker save <image>:<tag> from the operator's docker daemon into the host's docker daemon via the same SSH-tunnelled Docker API connection up uses (POST /images/load). The whole image crosses the wire every deploy with no dedup. Slower than unregistry on redeploy. No host dependencies beyond docker. For air-gapped hosts that can't pull the unregistry image, or for debugging transport issues.
Per-host progress bars track bytes transferred + rate live in tarball mode:
โ api:dev โ host-1 234.5 MiB @ 47.0 MiB/s (tarball)
โ web:dev โ host-1 done ยท 89.3 MiB (tarball)When standalone mode is the wrong fit
Fits: single host; hobby / prototype; air-gapped or restricted-network hosts.
Doesn't fit: many hosts (a registry is the natural hub); rollback by tag (registry keeps every pushed tag; local docker cache doesn't); audit requirements.
For your own registry without paying for one, see Self-hosted registry on a yoink host.
See also
- Deploy modes guide โ the build-origin ร distribution mental model.
- Self-hosted registry on a yoink host โ when standalone is too thin.
- Edit-save-deploy with
--watchโ pair with standalone for the tightest iteration loop.
Defense-in-depth web serving (Cloudflare + CrowdSec + Coraza)
A no-license-fee hardened web stack combining Cloudflare Free, CrowdSec, Coraza WAF, and sealed origin certs.
Run staging alongside prod
Run a staging environment on the same hosts as production using separate configs with namespaced services and networks.