The first deploy put all three Sites up serving and permanently unhealthy: every healthcheck run died with `/bin/sh: syntax error: unterminated quoted string`. The unit file carried the closing quote, but podman's stored config did not. Quadlet's parser strips a trailing double quote, so `node -e "fetch(...)"` reached the container as an unterminated shell string. The sibling apps never hit this because their HealthCmd is a bare `curl`. The check is now `wget -q -O /dev/null http://127.0.0.1:<port>/healthz` — busybox wget, no quoting, non-zero on anything but a 2xx — and the Dockerfile's own HEALTHCHECK says the same thing. The vars comment, the unit template and the deployment readme all say why it has to stay quote-free. Found from the running container rather than the play output: `podman healthcheck run` returned 1, `.State.Health.Log` held the sh error, and the app log showed it listening on 127.0.0.1:3000 the whole time.
59 lines
2.1 KiB
Django/Jinja
59 lines
2.1 KiB
Django/Jinja
# {{ container_name }} — rootless Podman Quadlet unit.
|
|
# Managed by Ansible (menu-admin/roles/menu-app). Do not edit by hand.
|
|
#
|
|
# This unit joins {{ network_name }}.network, which is created by
|
|
# roles/register — the same include that attaches the shared Caddy unit to it,
|
|
# so Caddy can resolve {{ container_name }} by name.
|
|
|
|
[Unit]
|
|
Description=Menu app for {{ host }} ({{ site }})
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Container]
|
|
ContainerName={{ container_name }}
|
|
Image={{ image }}
|
|
|
|
# keep-id maps the image's own account — `node`, uid/gid {{ app_uid }} — onto
|
|
# {{ podman_user }}, which keeps every file the app writes owned by
|
|
# {{ podman_user }} on the host: plain tar/rsync backups, no subuids, no
|
|
# `podman unshare`. scripts/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.
|
|
UserNS=keep-id:uid={{ app_uid }},gid={{ app_gid }}
|
|
|
|
Environment=MENU_ROOT=/data
|
|
Environment=MENU_SITE_CONFIG=/app/config/{{ site }}.site.json
|
|
Environment=SECURE_COOKIES=true
|
|
Environment=LOG_LEVEL=info
|
|
Environment=PORT={{ site_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={{ host_directory }}:/data
|
|
|
|
Network={{ network_name }}.network
|
|
|
|
# No PublishPort on purpose: Caddy reaches the app over {{ network_name }}_net,
|
|
# so nothing is exposed on the host.
|
|
|
|
# podman builds an OCI image by default and drops the Dockerfile's HEALTHCHECK
|
|
# instruction, so the same check is declared here and the play gates on it with
|
|
# `podman healthcheck run`. wget is busybox's; see vars/default.yml for why the
|
|
# command must not contain quotes.
|
|
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
|