You have a host. Now let's put an app on it. This chapter covers what the app must do, how to tell Yard about it, and how to see that it worked.
The four rules#
An app on Yard follows four rules. Most apps already follow them.
It has a
Containerfileor aDockerfile, at the repo root or inops/. (Or acompose.yml, for apps with more than one container. That's its own chapter.)It serves HTTP on port 8000, on all interfaces:
0.0.0.0, not127.0.0.1. Caddy reaches the container over the network, and127.0.0.1inside a container means "only me".It reads its settings from environment variables. You set them with
yard env set.It keeps its files in
/data. Everything else in the container is thrown away on the next deploy./datais a directory on the host, and it stays.
A Stario app made with uvx stario-create new follows all four rules already, so you can skip to the next section. For anything else, here's a minimal Containerfile, a Python app as an example:
FROM python:3.14-slimWORKDIR /appCOPY . .RUN pip install --no-cache-dir .CMD ["python", "-m", "myapp", "--host", "0.0.0.0", "--port", "8000"]Nothing about it is Yard-specific. If podman build and podman run -p 8000:8000 work on your laptop, the app will work on Yard.
Create the app#
Go to the app's checkout and run:
cd my-appyard app new --public my-app.example.comYard reads the repo URL and the branch from the checkout, so there's nothing to type. It writes a small record on the host: the repo, the branch to follow, and the trigger (push). The name comes from the repo, so github.com/you/my-app becomes the app my-app.
--public adds a bind: Caddy now sends https://my-app.example.com to the container my-app-app-1. Yes, "app" twice. Compose builds the name from the project (my-app), the service (app), and the instance number (1). You'll see that <name>-app-1 pattern everywhere in Yard.
Make sure my-app.example.com points at the VM first. (If you added the wildcard record in Set up a host, it already does.)
Push#
git pushThat's the deploy. Or, if there's nothing new to push, ask for one directly:
yard app deployThe first deploy builds the image from scratch, so it takes as long as your build takes. Later deploys reuse Podman's build cache.
See it#
yard statusname state cpu mem data rev bind deployyard-app-1 up 6h 0.1% 60MB 128MB 9c2c7e1 yard.example.com → yard-app-1 ok 43s · 2hyard-caddy-1 up 6h 0.3% 24MBmy-app-app-1 up 1m 0.4% 48MB 0B a1b2c3d my-app.example.com → my-app-app-1 ok 31s · 1mhost: 31% of 4 cores · 294MB of 7.7GB · 1.3GB of 40GBOne row per container. rev is the commit it runs. deploy says how the last deploy went, how long it took, and how long ago it was.
Give Caddy a few seconds to get the certificate, then open https://my-app.example.com.
When it doesn't work#
Three commands cover almost every first-deploy problem:
yard app logs --deploy # the build and the start: did the deploy work?yard app logs # the app's own output: did the app start?yard status --full # the host: DNS, certificate, CaddyThe usual suspects, in order of likelihood:
The build failed. The deploy log shows the build output. Fix it, push again.
The app listens on
127.0.0.1. The container runs, butyard statussaysno answer. Listen on0.0.0.0.The app listens on another port. Same symptom. Use 8000, or bind the real port:
yard bind set my-app.example.com my-app-app-1:3000.DNS doesn't point at the VM yet.
yard status --fullshows thednsandcertificaterows.
Settings and secrets#
Your app probably needs a few environment variables:
yard env set DATABASE_PATH=/data/app.sqlite3 SECRET_KEY=…yard app deployThe values live in one file on the host, readable only by the yard user. They never appear in an API response or a log. A new value reaches the container on the next deploy, which is why the second line is there. Day to day has more.
By the way#
No push? The check still notices. A new app record is enough: on the next check, at most five minutes away, Yard deploys the branch tip.
yard app deployis just faster.A branch can be an app too. Check out a branch called
redesignand runyard app new --public redesign.example.com. The app is namedmy-app-redesign, follows that branch, and lives next tomy-appwith its own data. That's a preview environment in one command.Inside the container, Yard also sets
PROJECT_NAME,PROJECT_DATA_DIR=/data,SHA(the commit), andPORT=8000. UseSHAto show the running version somewhere.Not inside a checkout?
yard app new --repo https://github.com/you/my-app --ref maindoes the same thing.