Skip to content

feat(media): rootless Podman + quadlet host CT 5114, youtarr migrated (ADR-0009 Phase 2a) - #304

Merged
ChrisonSimtian merged 2 commits into
mainfrom
feat/media-podman-host-302
Jul 26, 2026
Merged

feat(media): rootless Podman + quadlet host CT 5114, youtarr migrated (ADR-0009 Phase 2a)#304
ChrisonSimtian merged 2 commits into
mainfrom
feat/media-podman-host-302

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

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.network gives youtarr DNS resolution of youtarr-db via aardvark-dns. Compose gave that for free; quadlets do not. Verified live:

youtarr-db.dns.podman → 10.89.0.2

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

Member Mapping Why
youtarr none — no :U, no UserNS= writes to NFS; runs as container-root, which the default rootless mapping already sends to the podman user
youtarr-db keep-id:uid=999,gid=999 fixed uid 999 on local disk, data dir owned by podman

:U on 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 (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 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_healthy has no quadlet/systemd equivalent. After= waits for started, not ready, and a host-side ExecStartPre wait can't work either — 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 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_DIR is 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)

  • NFS mp0 + ensure-data-mount.sh hookscript applied by converge (3 applied, 0 failed)
  • Both units active; youtarr healthy
  • Migrated DB intact: channels=8, videos=95, jobs=645 — and the app log flipped from Auto-downloads disabled (fresh DB) to enabled (migrated), proving it reads the real config
  • NFS write → Plex read proven end to end
  • Boot survival 5s after an LXC restart, podman-auto-update.timer active

Three 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 --apply would touch the other 14 members.

🤖 Generated with Claude Code

… (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
@ChrisonSimtian
ChrisonSimtian merged commit fdebe1b into main Jul 26, 2026
2 checks passed
@ChrisonSimtian
ChrisonSimtian deleted the feat/media-podman-host-302 branch July 26, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 2a: migrate youtarr (CT 5113) to the Media rootless Podman host — NFS through three userns layers

1 participant