One push, start to finish

Follow one commit from your laptop to a live container.

The best way to understand Yard is to watch it work once. So let's push a commit to an app called heldso and follow it all the way to the browser.

Nothing on this page needs to be memorized. It's a story, and each later chapter zooms in on one part of it.

1. You push#

git commit -m "Fix the date on the history page"
git push

The commit lands on the forge: GitHub or Cursor Origin. Yard is not involved yet.

2. The forge knocks#

Yard is installed on the forge as an app, so the forge sends a webhook to the host: POST https://yard.example.com/hooks/github.

Here is the first design choice worth noticing. Yard does not trust the webhook. It does not read the commit from the request body. The webhook only means "something may have changed, go look". GitHub's signature is checked first, and then the request does one thing: it schedules a check.

Why so suspicious? Because webhooks are unreliable in both directions. Some never arrive, and anyone can send a fake one. If a webhook can only say "go look", a fake one costs a git ls-remote, and a lost one gets caught by the timer in step 3.

3. Yard looks#

A check runs git ls-remote for the branch the app follows, main by default, and compares the tip with the commit in Yard's own clone of the repo.

  • Same commit: nothing to do.

  • Different commit: deploy.

Checks also run on a timer, every five minutes, for every app that deploys on push. That's the safety net for the webhook that never came. And if you push five commits in ten seconds, Yard runs one check now and one more at the end of the ten seconds. You get one deploy, not five.

4. Yard fetches#

Yard updates its clone at /srv/yard/apps/heldso/repo to the new commit. The forge hands it a short-lived token for the fetch. The token is never written into the clone's .git/config.

On GitHub, the commit now gets a yellow dot: status pending, context yard.

5. A throwaway container does the work#

Now Yard starts a second container just for this deploy, called deploy-heldso. It's built from a tiny image that holds podman-remote and docker-compose, and it talks to the same Podman that runs everything else.

Why not run the build inside Yard? Two reasons. A build can be slow, noisy, and crashy, and none of that should touch the program that answers your commands. And the deploy container's log is the complete story of the deploy, kept in the journal after the container is gone.

Inside, a short shell script runs:

== build localhost/heldso:9c2c7e1a2b3c
built 2026-10-02T14:02:11Z
== start localhost/heldso:9c2c7e1a2b3c
== started localhost/heldso:9c2c7e1a2b3c

The image tag is the first twelve characters of the commit. So every image on the host says exactly which code is inside it.

6. Compose swaps the container#

The start line is docker-compose up --wait. Compose sees that the image changed, stops the old heldso-app-1, and starts a new heldso-app-1 from the new image.

The name stays the same, and that's the trick that makes the rest easy. Caddy's route for heldso.example.com points at heldso-app-1:8000, a name, not an address. The new container takes the name, and Caddy keeps sending traffic to it. Caddy also retries for up to ten seconds while the container starts, so a visitor during the swap sees a slow page, not an error.

7. Healthy, or back to the old one#

--wait waits until the container is healthy, for up to two minutes. "Healthy" means whatever the healthcheck in your compose file says, for example "/health answers 200".

If the new container never gets healthy, the script starts the previous image again and the deploy ends as reverted. Your site keeps running the old code, and the deploy log says what went wrong.

Without a healthcheck, "healthy" just means "running". That's fine to start with, but a healthcheck is what lets Yard catch a broken build. Your own compose file shows how to add one.

8. The check mark#

The deploy is done. Yard writes a deploy.end event to the journal with the outcome, and on GitHub the yellow dot turns into a green check that links to https://heldso.example.com. A revert or an error gets a red cross instead.

You can watch all of this from your laptop:

yard status               # heldso-app-1 shows the new revision
yard app logs --deploy    # the deploy log from step 5
yard events --app heldso  # every step above, on one timeline

The whole story in one breath#

You push. The forge pokes Yard. Yard checks the branch, sees a new commit, and fetches it. A throwaway container builds an image named after the commit and asks Compose to swap it in. Caddy keeps routing to the same container name. If the new container is unhealthy, the old image comes back.

By the way#

  • To roll back, deploy an older commit: yard app deploy --sha 4f1e2d0. If that image is still on the host, there's no build at all. Day to day has the details.

  • Only one deploy per app runs at a time. Two apps can deploy at the same time.

  • Cursor Origin works the same way, except that it has no commit statuses, so step 8 has no check mark there.