Worker Container Filesystem Layout
Thepoindexter-worker container runs the FastAPI process as appuser (UID 1001, defined in src/cofounder_agent/Dockerfile:62). All container-side filesystem layout decisions are downstream of this UID:
- The in-container
HOMEis/home/appuser. - Any Python code resolving
os.path.expanduser("~")orPath.home()returns/home/appuser. - A bind mount target of
/root/.poindexteris reachable for reads (the kernel honors the mount regardless of process UID) but is NOT the path that~/.poindexterexpands to.
Required mounts
docker-compose.local.yml mounts the host ~/.poindexter directory at two locations:
| Host path | Container path | Purpose |
|---|---|---|
~/.poindexter/podcast | /home/appuser/.poindexter/podcast | Generated podcast .mp3 files (one per post). |
~/.poindexter/video | /home/appuser/.poindexter/video | Generated video .mp4 files. |
~/.poindexter/generated-images | /home/appuser/.poindexter/generated-images | image-gen outputs before R2 upload. |
~/.poindexter/generated-videos | /home/appuser/.poindexter/generated-videos | Wan2 intermediate clips. |
~/.poindexter (entire dir) | /root/.poindexter | Legacy mount kept for code paths that still reference /root/.poindexter or use HOST_HOME=/root. |
~/.poindexter directory. This matters: if the host’s full ~/.poindexter were mounted at /home/appuser/.poindexter, then ~/.poindexter/bootstrap.toml would land in appuser’s home. brain.bootstrap.resolve_database_url() reads ~/.poindexter/bootstrap.toml as priority 1 (per docs/architecture/bootstrap.md) and the host’s database_url value points at localhost:5433 — unreachable from inside a container. The worker would crash on startup trying to connect to a host-only DSN even though DATABASE_URL is correctly set in the container’s env.
The legacy /root/.poindexter mount is intentional — anything that reads bootstrap.toml directly (the migration runner, certain CLI scripts) still works through that path. The two mount targets share the same host directory, so writes are bidirectionally visible.
Why this matters (the 2026-04-29 → 2026-05-12 silent failure)
Before 2026-05-12, the worker only mounted/root/.poindexter. Code that wrote to ~/.poindexter (resolved against appuser’s home) ended up in a container-local /home/appuser/.poindexter/ directory that:
- Was invisible to the host (no bind mount).
- Disappeared on container recreate.
- Was reached by the R2 upload step (
services/r2_upload_service.upload_podcast_episode) only if the upload ran in the same container process before the file vanished.
_spawn_background(generate_podcast_episode(...)) in services/publish_service.py), this meant every publish since 2026-04-29 produced a “Queued episode generation” log line but zero output on the host. 13 days of silent failure caught by the 2026-05-12 audit (Matt’s “podcasts and video generation working” request).
Adding new media output types
When introducing a new media output type:- Decide on a host directory under
~/.poindexter/. - Add a bind-mount entry in
docker-compose.local.ymlfor the worker service pointing at/home/appuser/.poindexter/<new-dir>. - Confirm the directory exists on the host (the
up -dflow won’t create missing host paths — it’ll bind-mount an empty directory that the container can write to, but Windows volume mounts can be finicky if the host path is missing). - If the legacy
/root/.poindextercode path needs visibility, the existing/rootmount already covers it.
Diagnostics
To verify the bind mounts at runtime:uid: 1001, lists 4 bind mounts under /home/appuser/.poindexter, and shows the historical host-side podcasts.