A container forgets. Each deploy throws the old one away and starts a fresh one from an image, and the image is the same on every machine. So where does an app keep its secret key, and where does it keep its database?
Yard keeps exactly two things for each app, outside every container: an env file for settings, and a data directory for files. This chapter explains both.
The env file#
Each app has one env file on the host, at /srv/yard/apps/<name>/env. It holds the settings the app reads from its environment: secrets, API keys, feature switches, anything that differs between your laptop and the server.
The format is the one you know from .env files:
APP_NAME=Hello from YardGOOGLE_CLIENT_ID=1234.apps.googleusercontent.comSESSION_SECRET=8c1f…One KEY=value per line. A key uses letters, digits, and _, and does not start with a digit. A value stays on one line. A value with spaces or odd characters can be double-quoted, with backslash escapes. Lines that start with # are comments, and an export in front of a key is ignored. Yard refuses a file that repeats a key, because one of the two values would win silently.
The file belongs to the yard user, with mode 600. No other user on the host can read it. Compose hands it to the container with env_file, so each key becomes an environment variable in the app.
A file is enough here, and a file is easy to reason about. No database or vault is needed. You can read it, copy it, and back it up. The env commands below are the only way it leaves the host.
Changing settings#
The yard env commands edit the file from your laptop. Inside the app’s checkout, they find the app themselves. Elsewhere, add --app <name>.
yard env cat # the whole fileyard env get SESSION_SECRET # one valueyard env set KEY=VALUE OTHER=VALUEyard env unset KEYyard env edit # the whole file in $EDITORyard env cat prints plain dotenv lines, so yard env cat > .env gives you a working copy for your laptop. yard env edit with no terminal reads the new file from stdin.
Saving never restarts#
Every change prints the same two lines:
Saved. notes keeps its old settings until the next deploy.Apply now: yard app deploy --app notesA running container cannot see a new value. Its environment was fixed when it started. So a new value reaches the app at its next deploy, and Yard leaves the timing to you.
Yard does not restart at once, because a change is often more than one key. You set a client ID, then its secret, then the redirect URL. Three restarts would put three half-configured versions online. With Yard, you make all three changes, then run one deploy. And a deploy with no new code is quick: the image exists, so the log says == reuse, and Compose only starts a new container with the new values.
The next push deploys the new values too, because every deploy reads the file.
Settings before the first deploy#
Some apps cannot start without a setting, such as a session secret. yard app new takes them up front, so they are in place before the first deploy:
yard app new -e SESSION_SECRET=8c1f…yard app new --env-file .env.productionyard app new --edit-env--edit-env is the friendly one. It opens your editor on a template from the repo: the first of .env.example, .env.sample, .env.template, or env.example at the top of the checkout. --edit-env PATH takes another file. Values from -e and --env-file come first, and the template adds the keys they do not set, with their comments:
# SecretsSESSION_SECRET=GOOGLE_CLIENT_ID=GOOGLE_CLIENT_SECRET= # Optional# OPENAI_BASE_URL=MODEL=gpt-5-miniFill in the values and save. Yard does not save a key that you leave empty, and it tells you so:
These keys are empty, so they are not saved: GOOGLE_CLIENT_SECRETSet them: yard env set --app notes GOOGLE_CLIENT_SECRET=…Yard drops an empty key because SESSION_SECRET= is worse than no key. The app would read an empty string, start, and fail later in a confusing way. With no key, a line like os.environ["SESSION_SECRET"] fails at start, and yard app logs names the key.
Keep the template in the repo. It holds no secrets, and it is the list of every setting the app reads. Yard does not scan your code for settings: a scan misses the key that a helper function reads.
What Yard sets for you#
Every repo app and image app that runs on Yard’s own compose files gets three variables:
PROJECT_NAME, the app name.PROJECT_DATA_DIR=/data, where the app keeps its files.SHA, the version that runs: the commit for a repo, the digest for an image, thesha256:of the file for a compose URL. Show it in a footer, and you always know what runs.
An image that Yard builds from a repo also gets:
PORT=8000, the port the app must listen on.STARIO_HOST=0.0.0.0andSTARIO_PORT=8000, so a Stario app listens where Caddy can reach it.STARIO_TRACER=json, so a Stario app writes each request as one JSON line to the journal.
A published image gets none of the second group. Yard runs it as its publisher built it, on the port the publisher chose.
A key that Yard sets in the compose file wins over the same key in your env file. That is Compose’s rule: environment: beats env_file:. So the env file cannot move a built app off port 8000 by accident.
A compose file of your own, from a repo or a URL, gets none of this by itself. Yard runs your file as it is, so it gets only what it names: you add env_file: [${APP_ENV}] and any variable you want. Your own compose file shows the lines.
Secrets stay secret#
Values leave the host only through the env commands, and only for a valid token. They never appear in yard app status, in an app answer of the API, or in a log. When you change a setting, the journal records the key names, never the values. And the env file never goes into a repo, because Yard keeps it on the host and nowhere else.
The data directory#
Each app has one data directory on the host, /srv/yard/apps/<name>/data, mounted into its container at /data. It is the one place inside a container that outlives the container. Write a file anywhere else, and the next deploy throws it away. Write it in /data, and it is there next week.
Put everything the app must not lose there: the SQLite database, uploaded files, a cache that is slow to rebuild. A Stario app from stario-create already does this. Its app/config.py keeps every file in PROJECT_DATA_DIR, which is /data on Yard and ./data on your laptop. The same code runs in both places, and only the variable differs.
yard status shows the size of each data directory, in its data column.
A compose file of your own mounts the directory itself, with ${DATA_DIR}:
volumes: - ${DATA_DIR}:/data - ${DATA_DIR}/postgres:/var/lib/postgresql/dataThe second line shows a common trick. A database image that keeps its files somewhere else gets a folder of the data directory mounted there. Everything the app owns still lives under one directory on the host.
Backups#
Everything an app owns on the host is in one folder, /srv/yard/apps/<name>. Its record, env file, and data are all there. The whole host is /srv/yard. A copy of that directory is a backup.
A running database can be caught in the middle of a write, so dump it first, then copy the dump:
sudo -u yard podman exec notes-app-1 sqlite3 /data/app.sqlite3 ".backup /data/backup.sqlite3"sudo -u yard podman exec umami-db-1 pg_dump -U umami umami > umami.sqlyard app rm deletes the app’s data directory along with everything else. It lists the directory and its size, and asks you to type the app name first. Copy the data out before you confirm.
Yard’s own settings#
Yard is an app named yard, so it has an env file too. A few keys change how Yard behaves:
YARD_CHECK_SECONDS, how often the timer checks every app. The default is 300, five minutes.YARD_PRUNE_DAYS, how old an unused image must be before the daily prune removes it. The default is 7, and 0 turns the daily prune off.
Set them like any other setting, then deploy Yard:
yard env set --app yard YARD_PRUNE_DAYS=14yard app deploy --app yardBy the way#
Want the same setting in several apps? Set it in each. Yard has no shared env, so one app’s settings can never leak into another.
The
sqlite3line above needssqlite3in the app’s image. A Python app can do the same withpython -cand thesqlite3module.