feat(media): rootless Podman + quadlet host CT 5114, youtarr migrated (ADR-0009 Phase 2a) - #304
Merged
Merged
Conversation
… (Phase 2a) Closes #302. Phase 2a of #283, on the platform from #284 and the pattern proven in #285. Adds the Media stack's generic container HOST (not a youtarr box) and migrates youtarr + its bundled MariaDB onto it as three quadlets. Replaces Docker host CT 5113, which is left STOPPED but NOT destroyed. First MULTI-container quadlet deployment, so it proves the pattern #303 needs: youtarr.network gives youtarr DNS resolution of youtarr-db via aardvark-dns (verified: youtarr-db.dns.podman -> 10.89.0.2). compose gave that for free; quadlets do not. userns decisions, per stack member rather than per host: - youtarr writes to the NFS share, so NO :U (it chowns recursively — 879 GB over NFS) and NO UserNS= (it runs as container-root, which the default rootless mapping already sends to the podman user). - youtarr-db is the opposite case, a fixed uid 999 on local disk, so keep-id:uid=999,gid=999 with the data dir owned by podman. THE OWNERSHIP PREDICTION WAS WRONG, and better than feared. I expected rootless writes to land under a different host uid than Docker's (101000 vs 100000), leaving two owner uids on the share. They don't: the NAS export already SQUASHES every incoming uid to 1024:100. A probe written by the podman container landed as 1024:100 — byte-identical to files the old Docker setup wrote — and Plex (CT 5008, same share) read it back. So the three userns layers are irrelevant to NFS ownership, and the NAS-side squashing #208 would have added as "the real fix" is already in place. Docs corrected accordingly. Two more findings, both now documented: - depends_on: condition: service_healthy has NO quadlet/systemd equivalent. After= waits for STARTED, not READY, and a host-side ExecStartPre wait can't work because youtarr-db is a podman-network DNS name unresolvable from the CT. Covered the systemd way: Restart=always + RestartSec=10, so youtarr crash-retries until MariaDB accepts connections. - A healthcheck IS expressible here, unlike Mate's: this image ships curl, so the command needs no quotes and survives systemd's parser. Confirms gotcha #10 is about QUOTED commands, not healthchecks generally. MariaDB's own check stays dropped — it needs quoting AND would put the root password in the unit file. YOUTUBE_OUTPUT_DIR is passed as the HOST path (/mnt/youtube) while data mounts at /usr/src/app/data — looks wrong, is exactly how CT 5113 runs, verified against the live container and preserved deliberately. Verified on CT 5114 (hpe-01): NFS mount + hookscript applied, both units active, youtarr healthy, migrated DB intact (channels=8 videos=95 jobs=645, and the app log flipped from "Auto-downloads disabled" on the fresh DB to "enabled" on the migrated one), NFS write→Plex read proven, and boot survival 5s after an LXC restart with podman-auto-update.timer active. Three secrets registered in Bitwarden SM + secrets.env.template, consumed as podman secrets: YOUTARR_DB_ROOT_PASSWORD / _AUTH_USERNAME / _AUTH_PASSWORD. Closes #302
From Phase 2a (#302), the first MULTI-container quadlet deployment. - #11: depends_on: condition: service_healthy has no quadlet OR systemd equivalent — After=/Requires= wait for STARTED, not READY. And a host-side ExecStartPre wait can't substitute, because container names only resolve inside the podman network while ExecStartPre runs on the CT. Do it the systemd way: Restart=always + RestartSec=10 and let it crash-retry. - Multi-container stacks need an explicit .network quadlet: compose gives service-name DNS for free, quadlets don't. Verified aardvark-dns resolution on CT 5114. - #10 sharpened: the constraint is on QUOTING, not healthchecks as a category. youtarr's curl probe works precisely because it needs no quotes, so check for a quote-free probe before giving up. Also: never put a secret in HealthCmd — it lands in the unit file. - userns section: check whether the NFS export SQUASHES before reasoning about ownership. Phase 2a's predicted 100000-vs-101000 split never materialised because the Synology export maps every incoming uid to 1024:100. Refs #302, #284
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #302. Phase 2a of #283, on the platform from #294 and the pattern proven in #285.
Adds the Media stack's generic container host (not a youtarr box) and migrates youtarr + its bundled MariaDB onto it as three quadlets. CT 5113 is left STOPPED, not destroyed.
First multi-container quadlet deployment — the pattern #303 needs
youtarr.networkgives youtarr DNS resolution ofyoutarr-dbvia aardvark-dns. Compose gave that for free; quadlets do not. Verified live:That's the main de-risking for #303 (ten containers, all needing name resolution) — Phase 1 was a single container with no peers, so this had never been exercised.
userns: the decision is per stack member, not per host
youtarr:U, noUserNS=podmanuseryoutarr-dbkeep-id:uid=999,gid=999podman:Uon the NFS volume would chown recursively — 879 GB across NFS. Never.My ownership prediction was wrong, and reality is better
I expected rootless writes to land under a different host uid than Docker's (
101000vs100000), leaving two owner uids on the share. They don't: the NAS export already squashes every incoming uid to1024:100.A probe written by the podman container landed as
1024:100— byte-identical to files the old Docker setup wrote — and Plex (CT 5008, same share) read it back. So the three userns layers are irrelevant to NFS ownership, and the NAS-side squashing that #208 would have added as "the real fix" is already in place. Docs corrected rather than left as written.Two more findings, documented
depends_on: condition: service_healthyhas no quadlet/systemd equivalent.After=waits for started, not ready, and a host-sideExecStartPrewait can't work either —youtarr-dbis a podman-network DNS name, unresolvable from the CT. Covered the systemd way:Restart=always+RestartSec=10, so youtarr crash-retries until MariaDB accepts connections.A healthcheck IS expressible here, unlike Mate's in #285 — this image ships
curl, so the command needs no quotes and survives systemd's parser. That confirms gotcha #10 is specifically about quoted commands, not healthchecks generally. MariaDB's own check stays dropped: it needs quoting and would put the root password in the unit file.One preserved oddity:
YOUTUBE_OUTPUT_DIRis passed as the host path (/mnt/youtube) while data mounts at/usr/src/app/data. It looks wrong, it's exactly how CT 5113 runs, and I verified that against the live container before converting — deliberately not "fixed".Verified on CT 5114 (hpe-01)
mp0+ensure-data-mount.shhookscript applied by converge (3 applied, 0 failed)Auto-downloads disabled(fresh DB) toenabled(migrated), proving it reads the real configpodman-auto-update.timeractiveThree secrets registered in Bitwarden SM +
secrets.env.template, consumed as podman secrets.Note on applying
Applied from an isolated single-member stack dir, since the engine still has no per-member converge filter and
converge stacks/Media --applywould touch the other 14 members.🤖 Generated with Claude Code