Run the Sites as rootless Podman Quadlets, and build with podman
The fleet on emily is rootless Podman with Quadlet units under ~/.config/containers/systemd and state in ~/podman-data, all behind one shared Caddy unit. This brings the menus in line instead of leaving them the last Docker deployment. The build moves with them: podman reads a Containerfile as readily as a Dockerfile, so the name now says which toolchain is real. The app is one .container and one .network file. Two details carry it. UserNS=keep-id maps the image's own node account onto the user that owns the units, so data/ and site.env stay owned by tanshu: plain tar backups, no subuids. provision-site-env.sh writes site.env with the same mapping rather than --user, which under rootless Podman would have produced a subuid-owned file the app could not read. And the Site registers its own network with Caddy by writing a drop-in beside caddy.container, so Caddy never lists its upstreams and never changes when a Site is added. The unit keeps the Docker-era container name for the scripts and muscle memory that look it up, while the Caddyfile proxies to a dotless NetworkAlias, because a container name with dots in it is a liability in DNS. The unit keeps podman's Pull=missing, so a restart or a reboot never depends on the registry being reachable, while the play pulls explicitly at deploy time. That is also where a missing `podman login` for the private registry now fails, with the instruction, rather than as a unit that will not start. copy-site-data.sh moves a Site's data across, and is not a plain rsync: /var/lib/menu.* on monoco belongs to uid 1000 while the ssh user there is 1001, so the read needs sudo; monoco cannot ssh to emily, so the workstation relays the tar; and the extraction runs as tanshu on emily, which is what makes the files land owned by the user keep-id maps the app onto. It proves the copy by comparing a SHA-256 of the whole tree against the source and checking every file's owner. Not yet done: the copies themselves and the DNS move, which need a human.
This commit is contained in:
1 parent
c412181159
commit
e68f8ab00d
23 files changed
+876
-389
No files matched your search
@@ -0,0 +1,60 @@
|
||||
# {{ container_name }} — rootless Podman Quadlet unit.
|
||||
# Managed by Ansible (menu-admin/roles/menu-app). Do not edit by hand.
|
||||
#
|
||||
# Joins {{ network_name }}.network, written by roles/register — the same include
|
||||
# that attaches Caddy to it. Caddy reaches the app by container name, so nothing
|
||||
# is published on the host and the app is not reachable from outside the Podman
|
||||
# network at all.
|
||||
|
||||
[Unit]
|
||||
Description=Menu app for {{ host }}
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
ContainerName={{ container_name }}
|
||||
Image={{ image_name }}:{{ image_tag }}
|
||||
|
||||
# keep-id maps the image's own account — `node`, uid/gid {{ app_uid }} — onto
|
||||
# {{ podman_user }}, which is what keeps every file the app writes owned by
|
||||
# {{ podman_user }} on the host: plain tar/rsync backups, no subuids, no
|
||||
# `podman unshare`. The same mapping is why scripts/provision-site-env.sh writes
|
||||
# site.env with --userns=keep-id.
|
||||
UserNS=keep-id:uid={{ app_uid }},gid={{ app_gid }}
|
||||
|
||||
# The alias is deliberately dotless. ContainerName keeps the Docker-era name so
|
||||
# operators and scripts find it, but a name with dots in it is a liability in
|
||||
# DNS, so {{ network_name }} is what the Caddyfile proxies to. Both resolve from
|
||||
# inside the network.
|
||||
NetworkAlias={{ network_name }}
|
||||
|
||||
Environment=MENU_ROOT=/data
|
||||
Environment=MENU_SITE_CONFIG=/app/config/{{ site }}.site.json
|
||||
Environment=SECURE_COOKIES=true
|
||||
Environment=LOG_LEVEL=info
|
||||
Environment=PORT={{ container_port }}
|
||||
|
||||
# Everything the app writes: data/ (originals, drafts, published, site.json,
|
||||
# sections.json) and site.env. The Site's own config is baked into the image and
|
||||
# read from /app.
|
||||
Volume={{ app_root }}:/data
|
||||
|
||||
Network={{ network_name }}.network
|
||||
|
||||
# podman builds an OCI image by default and drops the Containerfile's
|
||||
# HEALTHCHECK instruction, so the same check is declared here and the play gates
|
||||
# on it with `podman healthcheck run`. node is in the image.
|
||||
HealthCmd={{ health_cmd }}
|
||||
HealthInterval=30s
|
||||
HealthTimeout=5s
|
||||
HealthRetries=3
|
||||
HealthStartPeriod=30s
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
# The app validates its data directory before it serves and refuses to boot on a
|
||||
# fatal integrity problem, so a first start over copied data can take a moment.
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
Reference in new issue
Block a user