Self-Host Yoke
Run the Yoke API server on your own host: one docker compose bundle carrying
the published server image plus a Postgres 17 database. Your data stays on
hardware you control; engineers point their CLIs at your server instead of the
hosted platform.
Quickstart
Install Docker with Compose
on the server host first, then verify docker compose version succeeds.
Docker Desktop includes Compose; Docker Engine installations need the Compose
plugin. Init checks this prerequisite before writing or changing the bundle.
If Docker or Compose is missing, install Docker (with Compose) and run
yoke self-host init again; Yoke does not install them for you.
On the server host:
# 1. Install the CLI (also how engineer machines install it later).
curl -fsSL https://stage.upyoke.com/install | sh
# 2. Materialize the compose bundle. Writes docker-compose.yml, .env
# (carrying your name: first boot creates the first admin as you),
# and generated database credentials as owner-only secret files —
# the generated password is never printed. A marked block in
# .gitignore protects .env and secrets/ without replacing your rules.
yoke self-host init --admin-name "Your Name" --start
cd "${YOKE_MACHINE_HOME:-$HOME/.yoke}/yoke-server"
# 3. First boot writes the reusable administrator token to an owner-only
# file. The log names the path and never carries the token itself.
docker compose logs core
# 4. Attach your CLI (verifies the server and token before persisting
# anything). Keep the owner-only token file: it stays valid until you
# revoke it, and rerunning `yoke setup` on this host reconnects with it.
yoke connect http://127.0.0.1:8765 --token-stdin < secrets/first-boot-admin-token
# 5. Confirm the machine is wired up. This fails if the server is not
# answering, so a green run means the whole path works.
yoke status
The host command opens each mode 0600 input as its non-root operator and streams it on stdin through Docker exec. The container drops to its runtime user and writes private tmpfs copies; no host file is mounted or chowned and no credential is placed in an environment variable. The admin token returns over that same private handoff, outside Docker logs; the host atomically writes its owner-only token file and acknowledges durable storage before token creation commits. A failed handoff refuses with a reason and retry command.
Start/restart an existing bundle with yoke self-host init --dir PATH --protect-existing --start. This refreshes the packaged Compose template, preserving .env, database credentials, and the Postgres volume. Keep runtime settings in .env; reapply any custom Compose changes after a template refresh. Automatic container restart is disabled: after a host/daemon restart, run that host command again so it can reopen the protected inputs. Raw Compose starts alone cannot supply the handoff. Native Ubuntu rootful Docker is exercised by the manual bootstrap probe; rootless Docker and Fedora/SELinux remain unproved.
A new bundle requires --admin-name, written to .env as YOKE_ADMIN_NAME; first boot refuses to birth a universe without it and names the fix (set it in .env, then restart). The bundle defaults to ~/.yoke/yoke-server (YOKE_MACHINE_HOME/yoke-server when set), outside projects. yoke self-host init takes --dir, --port, and --image overrides. By default it resolves the installed CLI's immutable release manifest and writes that release's exact <repository>:<BASE.YYYYMMDDHHMM> image to .env. The repository defaults to ghcr.io/upyoke/yoke-server; set settings.server_image_repository in machine config, or choose Image repository in the self-host onboarding preview, to use a fork, mirror, or private registry. Enter a lowercase registry/repository without a tag or digest; it must carry the same immutable release version tags. Every fresh bundle therefore starts with matching CLI and server versions, and a later container restart keeps the same server. --image remains an explicit operator override. Generated credentials stay in host-owned files under secrets/ rather than .env, whose values Compose $-interpolates.
Refresh older bundles while preserving .env and database credentials:
yoke self-host init --dir /path/to/yoke-server --protect-existing
The command preserves operator-authored .gitignore rules and reports that credentials were not regenerated. The bundle and secrets/ must be real operator-owned directories; secrets/ must be mode 0700. It refuses symlinked paths and secrets already tracked by Git. Remove reported paths from the index, rotate any exposed credential, and retry.
Mac runtime and private-address preparation
On supported macOS choose Docker Desktop
or Colima with Docker CLI and Compose.
Check vendor OS/architecture support; current Desktop does not support older
macOS such as Monterey. Follow Colima/Lima's QEMU instructions for those hosts.
Install Compose as a Docker CLI plugin; docker compose version must report v2.
Start your chosen runtime yourself: open Desktop and finish its approved first
run, or run colima start in the owning user's Colima environment/context.
Require docker info to succeed before Start; Yoke installs or starts no runtime.
Setup's interactive and agent modes share this check and explain what to repair.
Loopback hosting needs no Colima host-address change. For a numeric Mac LAN or
Tailscale --listen address, Colima must forward that HOST address into its VM.
Read the installed colima start --help and vendor hostAddresses configuration.
With permission to stop that runtime, save its profile and ensure no unrelated
workload is running, then run colima stop followed by
colima start --network-host-addresses=true --network-address=false.
Preserve every other profile setting. The VM address is not the Mac address.
Verify a container published only on the chosen Mac private IP from another
machine; separately require a loopback-only control to remain unreachable there.
An unsupported flag or failed bind needs runtime configuration repair before
retrying the existing bundle with --protect-existing --start; widening to
0.0.0.0 is not the repair. These Colima settings do not apply to Docker Desktop.
Move an existing universe here
Point the import at a fresh or replaceable bundle, keep core stopped, and protect the archive as private control-plane data:
yoke self-host init --dir /path/to/yoke-server
chmod 600 ~/Downloads/acme-universe-20260714T120000Z.tar
yoke self-host import ~/Downloads/acme-universe-20260714T120000Z.tar \
--dir /path/to/yoke-server
The tar carries the database dump and freeze receipt (see Universe portability); that receipt supplies checksum verification. Type replace at the prompt, or pass --yes for a non-interactive replacement.
The command requires Docker with Compose, validates the bundle, and refuses while core runs. The archive must be a current-owner, single-link regular file with no group/world access. Compose starts only the database and streams the archive to a one-off process in the pinned image; it never bind-mounts the host archive.
Uploaded DDL never runs. Yoke creates the trusted destination schema, validates the bounded archive, and restores approved data and sequences in one transaction; retry replaces a failed or interrupted attempt.
Archives contain private records and hashed credentials; capability-secret data is excluded. Keep them owner-only and rotate secrets when custody changes. Restore revokes imported API tokens and browser sessions, re-credentials the archive's org-admin person (an archive with none refuses), and mints one replacement token. Save its one-time success output, then run the printed yoke self-host init --protect-existing --start and yoke connect steps.
If the restore reported success but its one-time result was lost before you
could save it, mint a recovery credential while core remains stopped:
cd /path/to/yoke-server
yoke self-host import --dir . --recover-credential
Save that command's raw_token, then start the service. Recovery atomically
revokes every prior import/recovery credential before minting its replacement,
so it is safe to repeat if another one-time result is lost.
Export over the server connection
After yoke connect selects this self-host server, an org administrator can
stream a portable archive without acquiring its database DSN:
yoke universe export --out ~/backups/
The CLI sends its bearer token only to the configured server, refuses
redirects, requires the archive media type, enforces the portability size and
time bounds, and publishes the owner-only destination file atomically. The
generated Compose bundle marks the runtime with
YOKE_SERVER_MODE=self-host; without that explicit marker the core endpoint
is hidden. Hosted Platform tenants continue through Platform's
fleet-coordinated download route instead of this self-host boundary.
Networking
By default the API publishes on loopback only (127.0.0.1:8765). To
serve your network, pass --listen ADDRESS at init, for example
yoke self-host init --admin-name "Your Name" --listen 100.101.102.103 --start.
This writes YOKE_API_PUBLISH=100.101.102.103:8765 in .env; teammates run
yoke connect http://100.101.102.103:8765. Use a LAN or Tailscale IP;
--listen 0.0.0.0 binds all IPv4 interfaces, so teammates substitute the
server's reachable private IP. IPv6 binds are supported too.
Private HTTP permits 10/8, 172.16/12, 192.168/16, 100.64/10, fd00::/8 and
*.ts.net; public HTTP is refused with HTTPS recovery. Each connect warns
that the machine token travels unencrypted: safe on Tailscale or a VPN;
use TLS on a shared LAN. Existing bundles keep their .env with
--protect-existing; edit YOKE_API_PUBLISH there and restart to change it.
Docker bypasses ufw/firewalld for published ports; bind to 127.0.0.1
behind a TLS reverse proxy, or restrict access upstream. yoke self-host init
warns when the configured publish address is beyond loopback, including on
--protect-existing --start; the warning never blocks setup or startup.
Set YOKE_API_TRUSTED_PROXIES in .env to the proxy IPs/CIDRs seen by core;
restart with yoke self-host init --dir PATH --protect-existing --start.
See proxy forwarding for headers and trust.
Engineer machines
See People and machines to connect engineers and approve your own machines.
Restore admin access
The host stays connected after setup. If this machine loses its connection,
rerun yoke setup on the host and choose Host a server here: it finds the existing bundle, restarts it, and
reconnects with secrets/first-boot-admin-token without asking anything new.
If that credential is lost or revoked, the first-boot and recovery bootstrap
is available on the server host:
docker compose exec --user yoke core python3 -m yoke_core.domain.api_tokens_cli bootstrap-admin --actor-name "Your name" --name recovery
Save the one-time token and reconnect with yoke connect <server> --token-stdin.
Once connected, use personal token controls to revoke the
lost token and manage your own tokens.
Web dashboard and browser sign-in
See Browser sign-in for token links, company sign-in and proxy configuration.
GitHub App server automation
Use a dedicated operator-owned App for server automation. GitHub App setup covers private-key ingress and runtime settings.
Upgrades
Running bundles stay on their exact image pin until you deliberately advance the pair. Upgrades use the configured image repository. From any directory, run:
yoke self-host upgrade --dir /path/to/yoke-server
The command performs a read-only preflight and shows the current image, target
release, exact target image, and ordered actions before asking you to type
upgrade. It then installs that release's CLI through the public installer
channel, atomically rewrites YOKE_SERVER_IMAGE, runs docker compose pull core, refreshes the Compose template, and restarts through the private host handoff. Use --yes only when an automated run
has already accepted that same plan.
An active source-checkout CLI refuses this command before preview or install,
just as yoke update does. Update the checkout with git, then rebuild its
source server with yoke core upgrade --from-checkout /path/to/yoke --build.
Checkout builds pass Git HEAD and the checkout's setuptools-scm version into
the image, so installed wheels pass version and migration-readiness checks.
The refusal preserves the CLI install, bundle image pin, and running server.
Failures name the stage and exact retry. The pin is unchanged when CLI install
fails; after the CLI and pin advance, a pull or restart failure leaves both
durable identities on the target and prints the two Compose recovery commands.
On every boot the entrypoint converges additive schema before serving;
data-transforming changes still use Yoke's governed migration runner. Foreign
keys onto environments.id match its live type so a universe still on text
keys reaches the ordered history that converts them. The boot also declares a
bounded idle_in_transaction_session_timeout for its own session and records
it as the role's database default; a managed Postgres whose role cannot alter
its own defaults degrades with an application_role_default_not_persisted
line on stderr rather than refusing to serve. Confirm
the running source identity through the build field on GET /v1/health.
Run a server from an edited Yoke checkout
Choosing Edit Yoke source in the wizard always activates that checkout for
the local CLI and harness. When the destination is guided self-hosting on the
same machine, the wizard also uses the existing local-core
--from-checkout PATH --build path so the private server image contains that
checkout's code. Connecting the edited client to an already-running team
server or stage.upyoke.com does not replace its image; the remote server keeps its
deployed build.
The ordinary published-image bundle remains pinned to YOKE_SERVER_IMAGE.
Building a custom image from the repository Dockerfile and selecting that
private/local image is still supported, but editing a checkout does not
silently repoint an unrelated remote bundle.
Take it back off
For full machine removal, use yoke uninstall. yoke self-host teardown removes the server install. It always stops and removes the
stack; everything further is opt-in and named for what it destroys. Run it
from anywhere; it defaults to the machine-home bundle. Name another bundle
with --dir PATH:
yoke self-host teardown # stop the stack; keep the data
yoke self-host teardown --remove-images \
--remove-bundle --destroy-universe # remove everything, prompts first
Without --destroy-universe the pgdata volume survives, so
yoke self-host init --protect-existing --start brings the same universe back. With it, the volume and
every item, event, and credential in it are gone; the command asks for consent
unless you pass --yes. --remove-images removes the images this bundle uses
and reports any another container still needs. --remove-bundle deletes the
files Yoke wrote, secrets/ and the admin token file included, and reports
anything else in the directory rather than deleting it.
Teardown atomically retires every machine connection pointing at this bundle's
server before stopping it, including each connection's unshared token file, so
no dead authority or credential is left behind. Pass --keep-connection to
leave all of them, or --activate ENV to name a connection for another server
that takes over as this machine's authority. Idle machine-config lock files
created by retirement are removed as part of teardown. Single-connection
retirement remains available on its own as
yoke connection remove ENV [--activate ENV]; removing your last connection
leaves the machine unconfigured, which yoke status then reports.
You own the operations
- Uptime is yours. Automatic restart is disabled. After a failure or host
restart, use
yoke self-host init --dir PATH --protect-existing --startto reopen the private inputs. You own monitoring and capacity. - Backups are yours. All state lives in the
pgdatavolume; useyoke universe exportfor portable archives and retain regular Postgres or volume snapshots for infrastructure-level recovery before upgrades and on a schedule. - TLS is yours. Use TLS for public access or shared LANs and declare proxy peers
in
YOKE_API_TRUSTED_PROXIES; publish the API only where intended.
Optional GeoIP context
Mount unpacked DB-IP Lite City and ASN .mmdb files read-only and set
YOKE_GEOIP_CITY_DB and YOKE_GEOIP_ASN_DB to their container paths.
Update the files monthly; missing files leave collection working without geo
fields. The trusted client IP is still stored. Follow the privacy and
DB-IP credit requirements.