papra: mount the volume where the app actually writes (R-156, last leg)
gates / gates (push) Successful in 1s

papra mounted papra_data:/app/data while the application writes to
/app/app-data, so its database sat in the container's writable layer: lost on
redeploy, and tarred nightly as an empty directory while the healthcheck stayed
green. Last of the three apps R-156 convicted.

Decided from the IMAGE, not the README. docker inspect of
ghcr.io/papra-hq/papra:26.6.1-rootless gives WORKDIR=/app and all three data
paths under ./app-data (DATABASE_URL, DOCUMENT_STORAGE_FILESYSTEM_ROOT,
PAPRA_CONFIG_DIR) — and /app/data does not exist in the image at all.

Reconfiguring the app to write to /app/data was available and deliberately not
taken: it enumerates data paths, so a fourth added upstream would escape to the
writable layer again, silently — this defect re-armed. Mounting the app's own
data ROOT captures every current and future path by construction.

Precondition checked rather than inherited: docker ps -a (including stopped) on
BOTH demo guests, plus the hub fleet view (two enrolled hosts, zero papra) —
both boxes were wiped and rebuilt today, so the 2 August evidence was re-measured.

Proven by the runtime gate in both directions: CLEAN with the self-test passing,
and BROKEN when the mount is reverted, with the exact R-156 evidence. Full
catalog_gates.py papra: all three gates OK.
This commit is contained in:
2026-08-03 11:29:51 +02:00
parent 7cb58ecdf8
commit 122bbeea48
3 changed files with 104 additions and 33 deletions
+30
View File
@@ -1,5 +1,35 @@
# Changelog
### papra — the volume is mounted where the app actually writes (2026-08-03, R-156, last leg)
**The third and last of the three apps that kept their data where backups never looked.** papra
mounted `papra_data:/app/data` while the application writes to `/app/app-data`, so its database sat
in the container's **writable layer**: lost on redeploy, and tarred nightly as an empty directory
while the healthcheck stayed green.
**Decided from the IMAGE, not the README.** `docker inspect ghcr.io/papra-hq/papra:26.6.1-rootless`
gives `WORKDIR=/app` and all three data paths under `./app-data``DATABASE_URL=file:./app-data/db/db.sqlite`,
`DOCUMENT_STORAGE_FILESYSTEM_ROOT=./app-data/documents`, `PAPRA_CONFIG_DIR=./app-data` — and
**`/app/data` does not exist in the image at all**.
**Why the mount moved rather than the app being reconfigured.** Pointing all three env vars at
`/app/data` would have worked, but it enumerates data paths: a fourth one added upstream escapes to
the writable layer again, silently, which is this defect re-armed. Mounting the app's own data ROOT
captures every current and future path by construction.
**Precondition checked, not inherited:** papra is deployed nowhere — `docker ps -a` (including
stopped) on both demo guests, plus the hub fleet view showing two enrolled hosts and zero papra
references. Both boxes were wiped and rebuilt on 3 August, so the 2 August evidence was re-measured.
**Proven by the runtime gate, in both directions.** `check-volume-persistence.py papra`**CLEAN**,
with its self-test passing on the same run. Red-proof: reverting the mount to `/app/data`**BROKEN**
with the exact R-156 evidence (`DATA in the writable layer at /app/app-data/db`,
`declared volume /app/data is EMPTY`). Full `catalog_gates.py papra`: all three gates OK.
**Note for the next run of that gate:** it needs **root** (it reads `/var/lib/docker/volumes`, mode
`drwx--x---`; as a normal user its own canary fails UNDETERMINED and it correctly refuses a verdict),
and it should be scoped to the app touched — unscoped it deploys all 53 templates.
## CI — the static catalog gate runs on every push (2026-08-02, R-168)
**No version bump, no build, no deploy** — this adds a workflow file only. Stated explicitly so the