← Blog

PrintStash v0.12.1: fiabilidad en el arranque del contenedor

Por qué algunos contenedores de la API entraban en bucle tras las migraciones, las dos variables de entorno que lo arreglan y a quién afecta.

versión

La v0.12.1 es un parche de fiabilidad de un solo arreglo para la imagen de la API. No cambia nada de las funciones, del esquema de la base de datos ni de la configuración. Si tus contenedores arrancan limpios hoy, actualizar sigue siendo solo descargar la imagen nueva, y puedes dejar de leer aquí. Si ejecutas PrintStash con actualizaciones automáticas al estilo de Watchtower, o si alguna vez has sobrescrito el comando del contenedor, este parche es para ti.

Qué salió mal

El 20 de agosto llegaron avisos de que printstash-api:latest estaba en bucle de fallos: el contenedor arrancaba, ejecutaba todas las migraciones de base de datos hasta el final, y moría justo después, una y otra vez (#77). Una de las personas que lo reportaron contó más de 250 intentos de reinicio a lo largo de varias horas. El frontend se mantuvo en pie todo el tiempo, lo que lo hacía parecer peor de lo que era: detrás había una API que nunca llegaba a estar sana.

El error al final de cada arranque fallido:

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)

Esa ruta pertenece a uv, el gestor de paquetes que la imagen usa en tiempo de construcción. La imagen construye su entorno de Python como root y luego ejecuta la aplicación con un usuario sin privilegios llamado printstash. En la v0.12.0, la construcción dejó de entregar a ese usuario el directorio de caché de uv, y el paso de instalación de Chromium de la imagen completa lo volvía a crear después con root como propietario. La ruta de la caché además se filtraba al tiempo de ejecución a través de una variable de entorno pensada para la etapa de construcción.

Nada de eso importaba en la vía de arranque que se publica. El entrypoint ejecuta las migraciones y después lanza uvicorn directamente desde el virtualenv, así que un despliegue de Compose de serie nunca invoca uv en tiempo de ejecución y nunca tocó la caché. El fallo necesitaba un segundo ingrediente: un despliegue que sobrescriba el comando del contenedor o que arranque la aplicación de otra forma a través de uv run, que es lo que todavía hacen bastantes archivos Compose antiguos y bastantes personalizaciones de quien administra. Cuando un despliegue así descargaba :latest, uv intentaba inicializar como usuario sin privilegios una caché cuyo propietario era root, fallaba, y se llevaba el contenedor con él. Que las migraciones terminaran limpias en cada ciclo hacía que el bucle pareciera incomprensible; nunca fueron el problema.

Qué ha cambiado

Dos variables de entorno, puestas al final del Dockerfile para que apliquen en tiempo de ejecución y dejen en paz a la etapa de construcción:

  • UV_CACHE_DIR=/tmp/printstash-uv-cache le da a cualquier invocación de uv en tiempo de ejecución un directorio de caché nuevo en el que el usuario printstash puede escribir.
  • UV_NO_SYNC=1 evita que uv vuelva a sincronizar el entorno en cada invocación. Las dependencias están fijadas y horneadas en la imagen en tiempo de construcción; volver a comprobarlas en tiempo de ejecución es trabajo desperdiciado incluso cuando sale bien.

Un test de regresión comprueba ahora los dos valores, su posición después de que se cree el usuario sin privilegios, y que el entrypoint y el CMD que se publican siguen ejecutando el virtualenv directamente.

Si mantienes una sobrescritura propia del comando, sigue funcionando, y ahora lo hace sin depender de los permisos de un directorio en el que nada de la imagen publicada escribe en tiempo de ejecución. Si además quieres dejar atrás la vía antigua por completo, la forma directa es la que la propia imagen publica:

command: ["/app/.venv/bin/uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Actualizar

Sin migraciones de base de datos y sin cambios de configuración. Descarga y reinicia:

Terminal window
docker compose pull
docker compose up -d --wait

Watchtower y los actualizadores automáticos parecidos no necesitan nada más; las imágenes arregladas son ya a lo que resuelve :latest. Como siempre, haz primero una copia de seguridad si vienes de muchas versiones atrás, y la guía de actualización tiene la rutina completa. La lista completa de cambios está en las notas de la versión v0.12.1.