This chapter takes you from an empty VM to a Yard that deploys on push. You run one script on the VM and a handful of commands on your laptop. Plan for about fifteen minutes, most of it spent waiting for packages.
What you need#
A VM with Ubuntu 26.04 LTS. Any provider will do. Two cores and 4 GB of memory hold a surprising number of small apps. Yard warns on another OS and refuses a Podman older than 5.
A domain you control. You'll point a name like
yard.example.comat the VM, and later one name per app.Port 443 open, for TCP and UDP. That's the only public port Yard uses, plus SSH for you. Port 80 can stay closed: Caddy gets its certificates over 443.
On your laptop: uv, which brings Python 3.14 for the
yardcommand.
1. Point the DNS at the VM#
Create an A record (and AAAA, if the VM has IPv6) for yard.example.com with the VM's address. Do the same for each app domain when you get to it, or add one wildcard record, *.example.com, and be done with DNS forever.
Do this first. Caddy asks for a certificate the moment a domain gets a route, and the request fails if the name doesn't point at the VM yet.
2. Run the install script#
SSH into the VM as root and run:
git clone https://github.com/Bobowski/yard.gitcd yard./install.sh --domain yard.example.comThe script never asks a question. It does this, in order:
Installs Podman, nftables, git, and a few helpers from Ubuntu's packages.
Creates a
yarduser and sets it up for rootless Podman. Everything from here on runs as that user, not as root.Creates
/srv/yard, where Yard keeps all its files.Forwards public port 443 to port 8443, for TCP and UDP, with a small nftables rule. Caddy is published on 8443 because a rootless user can't bind 443.
Sends container logs and Podman events to journald, with a persistent journal capped at 2 GB.
Creates the Podman network
yard.Clones Yard into
/srv/yard/apps/yard/repo, builds it, and starts two containers:yard-app-1(Yard) andyard-caddy-1(Caddy).With
--domain, writes the bind that putshttps://yard.example.comin front of Yard, and waits for it to answer.
At the end it prints a token:
token: 3f9a…bind: https://yard.example.com -> yard-app-1url: https://yard.example.comlocal: http://127.0.0.1:8000Copy the token. It's the admin token, and it's also in /srv/yard/tokens/admin.token if you lose it.
If something fails, the script stops with the reason and a hint. Fix the cause and run the script again. A second run repairs a half-finished install and leaves a working one alone.
3. Log in from your laptop#
uv tool install staryardyard login --url https://yard.example.com --token <token>yard statusThat's it. The script already wrote the bind — one public name, one container — so Caddy serves https://yard.example.com and proxies it to yard-app-1. yard login saves the address and the token in your user config, so later commands need neither.
Installed without --domain? Then Yard answers only on the VM at 127.0.0.1:8000. Reach it through an SSH tunnel and write the bind yourself:
ssh -N -L 8000:127.0.0.1:8000 root@yard.example.com &yard login --url http://127.0.0.1:8000 --token <token>yard bind set yard.example.com yard-app-1yard login --url https://yard.example.com --token <token>4. Connect a forge#
Yard needs to read your repos and hear about your pushes. It does that as an app installed on your forge. One command sets it up.
GitHub#
yard forge setup githubThe command prints a fresh webhook secret and opens a prefilled form for a new GitHub App: the name, the webhook URL, the push event, and two permissions (contents: read, commit statuses: write). Paste the secret into the form, click Create, and generate a private key. Then give the command the app ID and the key file. It opens the install page next. Pick the repos Yard may deploy, and the command notices when the install lands.
The webhook URL comes from the address you logged in with. That's why this step comes after step 3: GitHub must reach Yard on its public name.
Add --org my-org to create the app in an organization. If you stop halfway, run the command again and it continues where you were.
Cursor Origin#
yard forge setup originThe command prints the public key and the values for the form. Create the app at cursor.com/codebase/settings/apps with:
webhook URL
https://yard.example.com/hooks/originevent
repository.pushedscope
repository:contents:read
Then install it on your account and select all repositories. The command waits until it sees the install.
You can connect both forges. yard forge ls shows what's connected.
5. Let Yard upgrade itself (optional)#
Yard is an app on its own host, named yard. Point it at a copy of the Yard repo on a forge you connected, and a push to that repo upgrades Yard:
yard app set --app yard --repo https://github.com/you/yard --trigger pushSkip this if you'd rather upgrade by hand.
6. Check the host#
yard status --fullThis runs every host check: Podman, the network, Caddy and its routes, journald, each forge, DNS, certificates, and disk space. Each line says ok or what's wrong. When they're all ok, you're done, and Your first app is next.
By the way#
The script is plain
shand well commented. Reading it before you run it as root is a good habit, and this script rewards it.Rootless Podman can't bind ports below 1024. That's the whole reason for the 443 → 8443 forward. Inside its container, Caddy still listens on 443, so browsers that upgrade to HTTP/3 find it on the port they expect.
Make more tokens with
yard token new laptopand remove them withyard token rm laptop. You cannot remove the token you are using, and theadmintoken comes back at the next boot if it is removed.If
ufwis active on the VM, the script opens port 8443 for TCP and UDP itself.