# {{ 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