The playbook pulls the tag from the registry, creates the data root owned by the
app user, runs the container with its config baked into the image, waits for
health, then verifies the data directory as the app user so a permissions
mistake surfaces at deploy time. Caddy is pointed at the app with a stable
marker, so a temporary hostname can be checked beside the live site and the
cutover is one invocation with the retire flags set.
verify reports integrity problems and repair rebuilds derivatives from the
Originals, which is what makes the file layout trustworthy without a database.
The image runs TypeScript on Node 24 with no build step, writes only under
MENU_ROOT, and the provision script writes the untracked site.env using the
image's own hasher so the server needs no Node.
The admin signs in with the shared password, creates a Menu Set, drops photos
into it (shrunk in the browser first), reorders Pages by dragging or with
up/down buttons, previews the Draft exactly as customers would see it, and
publishes it over the previous set. Restore and delete close the loop. Mutations
need a session, a same-origin request and a CSRF token derived from the session;
a Draft's media is served only to a session holder.
Landing page, Section pages and archived sets rendered per request from the
store, with the approved design: hand-written CSS, self-hosted Archivo, a
copper pour line in the Section rail, an admin-only Thumbnail and a text page
index. Media is keyed by Page id so URLs survive publishing, and a Draft's
media needs a session. Includes the scrypt admin auth and the login throttle
the media rule needs.
The store keeps a Menu Set's state in the directory layout (drafts/ versus
published/) and its metadata in a manifest, with publishing as a single
directory rename. Editing is allowed on Drafts only, so a published menu can
never change under a customer. Stages 2 and 3 of docs/plan.md, with tests.