Run Tandoor as a rootless Podman Quadlet

Replaces the docker_container role with a Quadlet systemd user unit, and moves
state from /var/lib/tandoor to ~/podman-data/tandoor. Tandoor's entrypoint
starts as root and drops privileges, but root is what ends up owning the data,
so keep-id maps container root onto the connecting user and everything stays
owned by tanshu rather than a subuid.

The play does not run the rootless-Podman prerequisites: they are the shared
task file at caddy/roles/podman, and a host that has run caddy/playbook.yml has
them. Both integrations go through the shared includes rather than local copies
-- caddy/roles/register for the network and the Caddy drop-in, postgres/roles/
attach for the database -- so this app writes the same drop-in bytes as every
other service and a new service never means editing Caddy or Postgres.

Three ordering details are load-bearing:

- The register include runs in pre_tasks, before the unit is started. Quadlet
  fails unit *generation*, not merely startup, when a referenced .network file
  does not exist yet, so starting first reports "unit not found".
- roles/postgres flushes handlers immediately after attaching. The restart that
  joins Postgres to this network is a handler, so by default it runs at the end
  of the play -- after the app has already started and failed to resolve `db`.
- The role waits for a real HTTP response from inside the container instead of
  trusting systemctl is-active. The entrypoint runs Django migrations before
  gunicorn answers, and until it does the image's own nginx returns 502, which
  Caddy relays verbatim -- complete with `server: nginx`, which reads as a stray
  reverse proxy that is not actually there.

Also drops the dead nginx.conf.j2 and the network role, both superseded by the
shared includes, and moves .env into the role's templates/ as tandoor.env.j2,
rendered at mode 0600 and read through EnvironmentFile=.
This commit is contained in:
tanshu committed 2026-10-16 06:52:27 +05:30
1 parent 4297e5a71e
commit 4cd9308ae3
13 files changed
+654 -128

No files matched your search

+46 -7
View File
@@ -1,15 +1,54 @@
---
# ---------------------------------------------------------------------------
# Rootless Podman / Quadlet
# ---------------------------------------------------------------------------
# Duplicated on purpose from caddy/vars/default.yml: a play cannot include that
# file wholesale, because it also sets `host_directory` — to the Caddy data
# directory — and every playbook defines that for itself. Keep these three in
# sync with caddy/vars/default.yml. (Its fourth, podman_unprivileged_port_start,
# belongs to the podman tasks, which this play does not run: Tandoor listens on
# 80 inside the container and publishes nothing host-side.)
#
# podman_user is derived from the connection: this play carries no become, so
# the Quadlet units belong to whoever Ansible connects as. Requires gather_facts
# (set in playbook.yml).
podman_user: "{{ ansible_facts['user_id'] }}"
podman_data_dir: "{{ ansible_facts['user_dir'] }}/podman-data"
quadlet_dir: "{{ ansible_facts['user_dir'] }}/.config/containers/systemd"
# ---------------------------------------------------------------------------
# Tandoor
# ---------------------------------------------------------------------------
host: recipes.tanshu.com
docker_image: vabene1111/recipes:latest
docker_container: tandoor
docker_network: "{{ docker_container }}_net"
host_directory: tandoor
container_name: tandoor
unit_name: "{{ container_name }}.service"
# Fully qualified on purpose: rootless Podman resolves short names against
# unqualified-search-registries, and there is no TTY to answer a prompt when
# the systemd unit starts.
image: docker.io/vabene1111/recipes:latest
# The Quadlet file is <network_name>.network. caddy/roles/register pins the
# Podman network name to <network_name>_net (so: tandoor.network, tandoor_net).
network_name: "{{ container_name }}"
host_directory: "{{ podman_data_dir }}/{{ container_name }}"
# Port Caddy proxies to, inside the container (the image's own nginx).
container_port: 80
# The shared Postgres unit this app attaches itself to (postgres/playbook.yml).
# roles/postgres owns this app's drop-ins there and the "Restart Postgres"
# handler that applies them.
postgres_container: postgres
caddy_container: caddy
caddyfile_path: /var/lib/caddy/conf/Caddyfile
postgres_unit: "{{ postgres_container }}.service"
# The shared Caddy unit and its Caddyfile, both owned by caddy/playbook.yml.
caddy_name: caddy
caddyfile_path: "{{ podman_data_dir }}/{{ caddy_name }}/conf/Caddyfile"
snippet_block: |
{{ host }} {
reverse_proxy {{ docker_container }}:80
reverse_proxy {{ container_name }}:{{ container_port }}
}