What an app runs

Four answers to one question, decided once and written down.

Every app on Yard has an answer to one question: what runs? There are four possible answers. This chapter shows each one, how Yard picks the answer the first time, and how you change it later.

Four answers#

  • A Containerfile. Yard builds your code into an image. The Containerfile is in your repo, or Yard writes one for your language.

  • A compose file. Your own, in your repo, for an app with several containers, such as the app and a database. Your own compose file covers it.

  • An image. Something already built, from a public registry. No repo.

  • A recipe. A compose file at a URL, for a ready-made service. No repo.

The first two build from your repo. The last two run something that someone else built.

yard app status shows the answer on one line:

containerfile  yard

The first deploy decides#

You do not have to say anything when you create an app from a repo. On the first deploy, Yard looks at the commit and decides, in this order:

  1. A compose file: compose.yml or compose.yaml, at the root or in ops/.

  2. A Containerfile or a Dockerfile, at the root or in ops/.

  3. A language that Yard knows. Yard writes a Containerfile for it.

Yard saves the answer on the app record. The output of that deploy, which yard app new and yard app deploy print, starts with what it chose:

== runs containerfile yard (stario: pyproject.toml declares stario)

From then on, Yard keeps that answer. A later commit does not change it, even when the commit adds a compose file or a Containerfile. Changing what an app runs is your decision, in the same way that changing its settings is.

The languages Yard knows#

When a repo has no compose file and no Containerfile, Yard reads its files:

  • Stario: pyproject.toml declares stario, and uv.lock is committed. The Containerfile is the one that stario-create writes.

  • Python: pyproject.toml with uv.lock, or a requirements.txt. The image runs app/main.py, main.py, or app.py, the first one it finds.

  • Go: go.mod and one main package, at the root or in cmd/. The binary runs on a distroless image.

  • Node: package.json with a start script. The image installs with npm, runs npm run build when there is a build script, and starts with npm start.

Yard does not guess. Two languages in one repo, a Go repo with several main packages, or a Node repo that uses pnpm stop the first deploy with a message that says what Yard found. Add a Containerfile, and deploy again.

Every Containerfile that Yard writes sets PORT=8000, and the app must listen on that port. The file has no healthcheck, because Yard cannot know where your app answers. Add a HEALTHCHECK when you take the file into your repo. Healthchecks explains why it matters.

Look at the file#

yard app cat containerfile
yard app cat compose

cat prints the file that the app runs: the file in your repo at the deployed commit, the file that Yard wrote, or Yard's own compose file. A Containerfile that Yard wrote starts with a line that says where it came from:

# Written by Yard for a stario app (pyproject.toml declares stario). To change it, copy it into your repo: yard app cat containerfile --app my-app > Containerfile

Change it#

Two commands change the answer. Neither one deploys. Each one prints the command that does.

To use a file in your repo, name it:

yard app set --containerfile Containerfile
yard app set --compose deploy/stack.yml

The path can be any file in the repo. It does not need one of the standard names. If a later commit removes the file, that deploy fails and says so, and the running app stays up.

To use the Containerfile that Yard writes, even when the repo has its own, name yard:

yard app set --containerfile yard

The next deploy writes it for the repo's language, and later deploys keep it.

To let Yard decide again, as on the first deploy:

yard app detect
my-app: the next deploy decides what to run again (was: containerfile yard).
Deploy now: yard app deploy --app my-app

detect is also how a written Containerfile gets newer. Yard keeps the file it wrote, even after a Yard upgrade brings a better template. The next deploy after detect writes the file again from the current template.

The written file is a starting point. When you want to change it, take it into your repo:

yard app cat containerfile > Containerfile
git add Containerfile && git commit -m "Own the Containerfile" && git push
yard app set --containerfile Containerfile

An image#

An image app has no repo. It runs an image that a public registry already holds:

yard app new --image ghcr.io/you/api:1.4 --public api.example.com

The app's name comes from the image: api. Its container is api-app-1. It is on the yard network, with its own env file and /data, like any other app.

The tag decides how far the app follows:

  • postgres:17 follows every 17.x release, and never moves to 18.

  • postgres:17.2 gets only the patch releases of 17.2.

  • postgres:17.2.1 stays on that release, plus any rebuild that the publisher pushes under the same tag.

  • latest follows everything, including the next major version.

A tag is required. Yard refuses postgres alone, so an app never follows latest by accident.

At every check, Yard asks the registry which image the tag points to. When the tag moves, a push app deploys the new image, exactly as it deploys a new commit. A manual app shows the new image in yard status and waits for yard app deploy. Yard always runs the exact image, ghcr.io/you/api:1.4@sha256:…, so a restart never changes what runs.

To roll back, name an older tag and deploy:

yard app set --app api --image ghcr.io/you/api:1.3
yard app deploy --app api

Yard runs the image as its publisher built it. It does not set PORT or any other variable that changes how the image listens. So the bind must dial the port the image already uses. That is 8000 by default; --port names another one:

yard app new --image docker.io/library/nginx:1.27 --port 80 --public www.example.com

The image gets the app env, /data, and three variables: PROJECT_NAME, PROJECT_DATA_DIR=/data, and SHA, the digest. An image that keeps its files somewhere else than /data needs a compose file, or a recipe.

An image that needs settings before it can start, such as a database password, is created with --no-deploy. Set its env, then deploy:

yard app new --image docker.io/library/postgres:17 --no-deploy
yard env set --app postgres POSTGRES_PASSWORD=…
yard app deploy --app postgres

A recipe#

A recipe is a compose file at a URL, for a ready-made service. Yard's own recipes live in the recipes folder of the Yard repo.

yard app new --recipe uptime-kuma --public status.example.com

A name means a recipe from that folder. A full https:// URL means a recipe from anywhere:

yard app new --app analytics --recipe https://example.com/recipes/umami.yml

A recipe is one compose file, and it follows the same rules as your own: a service app on the yard network, with ${APP_ENV} and ${DATA_DIR}. A comment line such as # port: 3001 tells --public which port to bind.

A recipe app follows its URL the way a code app follows its branch. At every check, Yard fetches the file. When its content changes, a push app deploys it. A manual app shows the change in yard status and waits. Yard keeps a copy of the file it runs, so a restart or a revert needs no network.

Yard's own recipes pin every image by digest. A new version of a service arrives as a commit to its recipe. A new major version gets a new recipe name, so the push trigger never moves a database to a new major version by surprise.

To stay on one version of a recipe, use the raw URL of one commit instead of main. To roll back, point --recipe at the raw URL of an older commit.

When the source fails#

An image app and a recipe app depend on something outside the VM. The rule is simple: the running app keeps running.

  • The registry or the URL does not answer. Yard tries again at the next check, and yard status --full shows a source <name> warning until it works.

  • The recipe is not a valid compose file, or it breaks the rules. The deploy fails before anything starts, and the old version keeps running.

  • The new version never gets healthy. Yard reverts to the one before, as it does for code.

By the way#

  • Following an image or a recipe means that its publisher's changes deploy to your host. For a source that you do not control, create the app with --trigger manual, and look at each update before you deploy it.

  • yard app deploy --sha is for code apps. An image app rolls back by tag, and a recipe app by URL.

  • Prune removes only the images that Yard built. Pulled images stay on the disk until you remove them with Podman.