Self-hosted registry on a yoink host
Run registry:2 as a yoink-managed service, expose it over the tailnet, and push images to it.
The middle ground between "real remote registry" (ghcr.io, etc.) and "no registry at all" (services with a build: block, shipped from your local docker daemon by plain yoink up): run a registry:2 container as a yoink-managed service on one of your hosts, expose it via tailscale, and point image: at the tailnet hostname.
Best for "I want a registry but I don't want to pay for one and I don't want to run it on a separate machine."
Setup
Declare the registry as just another service in your yoink.yaml
services:
- name: registry
image: registry
tag: "2"
networks: [registry]
run:
port: 5000
volumes:
- "registry-data:/var/lib/registry" # persistent storage
options:
memory: "256Mi"Bring it up
yoink up --service registryPoint your app's image: at the tailnet hostname
services:
- name: api
image: registry.my-tailnet.ts.net:5000/api # tailnet-routed registry path
tag: dev
build:
context: .Use it like any registry
yoink build api --push # docker build → docker push to your tailnet registry
yoink up --service api # docker pull from the tailnet registry on each hostWhy this works
- Auth: tailscale ACLs are the registry's auth surface. If your tailnet is correctly scoped (only operators + CI runners + hosts can reach it), no
docker login/ registry password needed. - Storage: the
registry-datavolume keeps tags across restarts. - Distribution: every host you deploy to has to be on the same tailnet (or be reachable to the registry host's
:5000). Yoink's existing tailnet-required transport already handles this.
Caveats
- The registry host is a single point of failure for image pulls. If it dies during a deploy, every host that doesn't already have the image cached locally fails to pull. Mitigation: pin to a real cloud registry for prod-critical workloads, use the self-hosted registry for staging / hobby / non-critical.
- No replication. If you need image distribution across regions, this isn't the right answer; use a real registry (Depot, ghcr.io, ECR).
- No automatic garbage collection. Old image layers accumulate; periodically run the registry's GC inside the container.
See also
- Three deploy modes — when registry vs. registry-less (
build:blocks shipped via unregistry over SSH) makes sense. - Multi-host distribution — pinning services to specific hosts when you've split images across registries.
Adding a service via `yoink add`
Pick a vetted template (postgres, redis, meilisearch, …), answer prompts, get a sealed-secret-ready service fragment.
Run Terraform (or any infra glue) from a hook
Provision DNS, certs, databases, or any external resource the service depends on as a yoink pre-deploy hook — running directly on the operator's machine with the sealed bundle wired in via a `secrets_profile`.