Replaces the docker_container playbook, which published 5432 on 0.0.0.0 and kept
its data in /var/lib/postgresql. Postgres is now the base Quadlet unit, the same
shape as Caddy: it knows nothing about its clients, and a service attaches itself
by dropping a file into postgres.container.d/ via postgres/roles/attach. Nothing
here changes when a service is added or removed.
Pinned to 18, which is two decisions rather than one tag. Postgres 18 moved both
the image's VOLUME (/var/lib/postgresql/data -> /var/lib/postgresql) and its
default PGDATA (/var/lib/postgresql/data/pgdata -> /var/lib/postgresql/18/docker),
and the 18 entrypoint refuses to start when it finds a cluster in the old layout.
So postgres_major is the single knob: it picks the image tag and the PGDATA path
together, and the mount is now the parent directory. That also settles the
migration: a PGDATA copy cannot cross a major version, so monoco's 17.5 cluster
comes across as a pg_dumpall.
Three things that bit us and are worth keeping:
- PGDATA and its version directory are created by the role, not left to the
container. The entrypoint runs as container-root, which under keep-id is a
subuid on the host, and it only chowns PGDATA and its contents -- so
~/podman-data/postgres/18 would have been stranded subuid-owned, and the host
user could not even rm -rf it without podman unshare.
- NetworkAlias= applies to every network the container joins, which is what a
database hub wants, but it is one fixed value in a shared drop-in rather than
per-service: two services writing their own would make Quadlet pass
--network-alias twice. It is not in postgres.container itself because on a
fresh host that unit has no Network= at all.
- The credentials live in a 0600 file read with EnvironmentFile=, not in
Environment= lines: the unit is world-readable in ~/.config.
The play no longer uses become, and does not apply the rootless-Podman
prerequisites -- those are the shared task file at caddy/roles/podman, and the
three variables they need are duplicated at the top of vars/default.yml.
migrate-from-monoco.md records the one-time 17.5 -> 18.6 move: the dump, transfer
and restore commands, the checks that prove the restore is complete (per-database
table counts against the source), and the verified result.
The dead nginx/php-fpm templates in postgres/files/ are removed.
Replaces the docker_container role with Quadlet systemd user units under
~/.config/containers/systemd, and moves state from /var/lib/caddy to
~/podman-data/caddy.
Caddy is now the base unit and no longer lists its upstream networks. Each
service registers itself by writing its own <name>.network unit plus a drop-in
into caddy.container.d/, so adding or removing a service never means editing the
Caddy playbook. Drop-ins are used rather than blockinfile because this playbook
re-templates caddy.container on every run, which would wipe appended blocks.
The play runs as the connecting user instead of using become: the rootless units
belong to it, so the connection's PAM session is what makes systemctl --user
work without setting XDG_RUNTIME_DIR. podman_user and the data directory are
derived from facts, so the play fails loudly if that resolves to root.
Two things that bit us and are worth keeping:
- The privileged-port sysctl is applied inline with sysctl -w, not via a
handler. Handlers only run at the end of the play, by which point Caddy has
already failed to bind 80/443.
- The Caddyfile is seeded with a minimal valid config. Caddy exits 1 with a
bare "EOF" on an empty config file, so touching it into existence made the
service crash-loop.
Two plays: bootstrap as the provider account (ubuntu, over bootstrap.pem), then
everything else as the user that first play just created. The split is forced
rather than stylistic -- the second play logs in as that user, so its
authorized_keys entry and its NOPASSWD sudoers entry both have to exist before
it starts. That is why the key tasks live in the 'user' role and 'wheel' runs
in the first play: move either into play 2 and it fails with an auth error or
"Missing sudo password".
Both accounts and both keys are variables. The inventory sits beside the
playbook and is read via ansible.cfg, which Ansible picks up from the current
working directory rather than the playbook's, so it has to be run from this
directory. bootstrap.pem is gitignored -- it is the provider key for the host.
It lives beside the app it deploys now, with podman building the image and an
inventory carrying one entry per Site (which is what the local 'hosts: monoco'
edit was reaching for). Nothing in this repo referenced it.
The app's health check ran on the target host against the container name, which
only resolves inside the Docker network, so the wait could never succeed. Poll
the image's HEALTHCHECK instead and fail with a readable message. Also pin
site.env to the app user when it exists, and say how to install the provisioning
script on the server.
Deploys the menu app for one Site: pulls the tag, creates the data root, runs
the container against a config baked into the image, waits for health, verifies
the data directory, and points Caddy at the app with a marker it can update in
place. The parallel run uses a temporary hostname; cutover sets the retire
flags.
Updated the web nginx.conf and the source is not given in the readme.
Restricted to gondor.
Webserver image and container names are now picked from the vars file.
Restricted it to gondor host.
Moved back to postgres on the same host. This does away with the etc_hosts link, but kept the variable and playbook entry as a template if and when needed.