PrintStash v0.12.1: container startup reliability
Why some API containers crash-looped right after their database migrations finished, the two environment variables that fix it, and who needs to care.
v0.12.1 is a one-fix reliability patch for the API image. It changes nothing about features, the database schema, or configuration. If your containers start cleanly today, upgrading is still just pulling the new image, and you can stop reading here. If you run PrintStash with Watchtower-style automatic updates, or you have ever overridden the container command, this patch is for you.
What went wrong
On August 20, reports came in that printstash-api:latest was crash-looping: the container
started, ran every database migration to completion, and died immediately after, over and
over (issue #77). One reporter
counted more than 250 restart attempts across several hours. The frontend stayed up the whole
time, which made it look worse than it was: behind it sat an API that never became healthy.
The error at the bottom of every failed start:
error: Failed to initialize cache at `/tmp/.uv_cache` Caused by: failed to open file `/tmp/.uv_cache/sdists-v9/.git`: Permission denied (os error 13)That path belongs to uv, the package manager the image uses at build time. The image builds
its Python environment as root and then runs the application as an unprivileged user named
printstash. In v0.12.0, the build stopped handing the uv cache directory to that user, and
the full image’s Chromium install step recreated it as root-owned afterward. The cache path
also leaked into runtime through an environment variable meant for the build stage.
None of that mattered for the shipped startup path. The entrypoint runs migrations and then
executes uvicorn straight from the virtualenv, so a stock Compose deployment never invokes uv
at runtime and never touched the cache. The failure needed a second ingredient: a deployment
that overrides the container command or otherwise starts the app through uv run, which
plenty of older Compose files and operator customizations still do. When such a deployment
pulled :latest, uv tried to initialize the root-owned cache as the unprivileged user,
failed, and took the container down with it. Migrations finishing cleanly every cycle made
the loop look baffling; they were never the problem.
What changed
Two environment variables, set late in the Dockerfile so they apply at runtime while leaving the build stage alone:
UV_CACHE_DIR=/tmp/printstash-uv-cachegives any runtime uv invocation a fresh cache directory theprintstashuser can write.UV_NO_SYNC=1stops uv from re-syncing the environment on each invocation. Dependencies are locked and baked into the image at build time; re-checking them at runtime is wasted work even when it succeeds.
A regression test now asserts both values, their position after the unprivileged user is created, and that the shipped entrypoint and CMD still execute the virtualenv directly.
If you maintain a custom command override, it keeps working, and it now does so without depending on the permissions of a directory nothing in the shipped image writes to at runtime. If you also want to drop the legacy path entirely, the direct form is what the image itself ships:
command: ["/app/.venv/bin/uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]Upgrading
No database migrations and no configuration changes. Pull and restart:
docker compose pulldocker compose up -d --waitWatchtower and similar auto-updaters need nothing extra; the fixed images are already what
:latest resolves to. As always, take a backup first if you are many versions behind, and
the upgrade guide has the full routine. The complete change list
lives in the v0.12.1 release notes.