Por qué `latest` es el verdadero culpable
Cuando una imagen Docker etiquetada como latest se actualiza en el registry, tu docker compose pull la descarga en silencio. Sin advertencia, sin diff. Reinicias el stack y descubres que el nuevo contenedor no puede leer los datos que dejó el anterior.
Es exactamente el riesgo con Meilisearch. La base de datos lleva el número de versión del motor que la escribió: al subir de versión, el nuevo motor se niega a abrirla mientras no se le pida explícitamente migrarla con --upgrade-db (o la variable MEILI_UPGRADE_DB). Con una política de reinicio, el contenedor entra entonces en crash-loop. Y una vez migrada la base, la imagen anterior ya no puede abrirla: Meilisearch no admite la vuelta atrás. La única red de seguridad limpia es un dump o un snapshot del volumen creado antes de la actualización — imposible una vez migrada la base o bloqueado el contenedor.
El tag latest no resuelve a la misma imagen según el momento del pull. Dos desarrolladores que ejecutan docker compose pull con doce horas de diferencia pueden descargar versiones distintas. En un stack de producción, esta ambigüedad es inaceptable. La solución no es evitar las actualizaciones: es controlar explícitamente qué versión está en ejecución y decidir conscientemente cuándo pasar a la siguiente.
Qué cubre esta guía — y qué no
- Qué cubre esta guía: el protocolo paso a paso para actualizar un stack Docker Compose en un VPS — version pinning, backup de volúmenes, revisión de notas de versión, rollback rápido, healthchecks como red de seguridad.
- Cubierto en otras guías: el checklist de hardening inicial (
docker-compose-production-checklist), la configuración dedepends_onyservice_healthy(docker-compose-depends-on-healthcheck), y la comparativa de herramientas de actualización automática como Watchtower o Diun. - Lo que esta guía no recomienda: Watchtower o cualquier herramienta de auto-pull en producción — ese es precisamente el antipatrón que ilustran los casos de breaking changes.
- Público objetivo: desarrolladores y agencias que gestionan uno o más stacks Docker Compose en producción en un VPS, con acceso root y volúmenes persistentes.
Paso 1 — Fija todas tus imágenes
La primera acción, antes de cualquier actualización, es reemplazar cada image: getmeili/meilisearch:latest o image: supabase/postgres por una versión explícita.
Dos formas son aceptables:
- Tag de versión: image: getmeili/meilisearch:v1.53.0 — legible, versionable en git, fácil de parchear.
- Digest SHA256: image: getmeili/meilisearch@sha256:abc123… — inmutable, garantiza que descargas exactamente el mismo artefacto en cada redespliegue, incluso si el tag fue sobreescrito.
Para obtener el digest de una imagen ya en ejecución:
docker inspect --format='{{index .RepoDigests 0}}' getmeili/meilisearch:v1.53.0Una vez fijadas tus imágenes, haz commit del docker-compose.yml en git. Cada bump de versión se convierte en un commit, lo que te da un historial claro y un rollback trivial (git revert + docker compose up -d).
Protocolo de actualización — los 5 pasos
Lee las notas de versión antes de hacer pull
Primero, consulta las notas de versión de la nueva versión. Busca las palabras
breaking,migration,incompatible,pg_upgrade,dump. Esto no es opcional: Supabase documentó explícitamente que actualizar de PostgreSQL 15 a 17 requiere unpg_upgrademanual — el contenedor PG 17 se niega a arrancar sobre un volumen PG 15, y el proceso de inicialización no migra los datos automáticamente.Para Langfuse v4 (v4.0.0 publicada el 29 de julio de 2026), los SDKs Python v2 y anteriores son rechazados en la ingesta por el nuevo stack — un cambio disruptivo que afecta a todos los servicios cliente que rastrean mediante la API antigua.
Tres minutos de lectura te ahorran varias horas de recuperación de datos.
Haz backup de los volúmenes antes del pull
Nunca hagas pull sin tener un backup utilizable. Para volúmenes nombrados, dos enfoques:
Dump aplicativo (recomendado para bases de datos) — el servicio debe estar
healthyantes de hacer el dump:docker compose exec -T db pg_dump -U postgres -Fc mydb > backup_$(date +%Y%m%d_%H%M%S).dumpLa opción
-Tdesactiva el pseudoterminal: sin ella, el flujo binario del dump puede alterarse.Snapshot de volumen en crudo — útil para almacenes binarios (Meilisearch, Redis, MinIO):
docker run --rm \ --volumes-from $(docker compose ps -q meilisearch) \ -v $(pwd)/backups:/backup \ alpine tar czf /backup/meili_$(date +%Y%m%d_%H%M%S).tar.gz /meili_dataVerifica que el backup es legible antes de continuar. Un archivo de dump corrupto descubierto durante la recuperación es el escenario más costoso que existe.
Descarga la nueva imagen y pruébala fuera de producción
Actualiza el tag en tu
docker-compose.yml, luego descarga la imagen sin reiniciar el servicio:docker compose pull meilisearchSi tu entorno lo permite, prueba la nueva imagen sobre un clon del volumen en un entorno ddev o una VM de staging antes de tocar producción. Revisa los logs de arranque en busca de errores de migración:
docker compose up -d meilisearch docker compose logs -f meilisearchEspera a que el healthcheck alcance el estado
healthyantes de validar. Un servicio que arranca pero no estáhealthytodavía no es un servicio listo.Verifica los healthchecks
Un healthcheck bien configurado es tu primera línea de detección. Debe estar presente en cada servicio crítico del stack, en formato Compose v2:
healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:7700/health || exit 1"] interval: 10s timeout: 5s retries: 5 start_period: 30sEl campo
start_periodes crítico para servicios de arranque lento (bases de datos, motores de búsqueda): evita que Docker declare el contenedorunhealthydurante la fase de inicialización y desencadene un reinicio prematuro.Consulta
docker-compose-depends-on-healthcheckpara la configuración completa deservice_healthyen PostgreSQL — el mismo principio aplica a cualquier servicio que necesite un período de calentamiento.Rollback si algo falla
Si la nueva versión no arranca o produce errores, el rollback debe tomar menos de dos minutos. El procedimiento:
1. Vuelve al tag anterior en
docker-compose.yml(ogit revertsi hiciste commit del bump).
2. Reinicia únicamente el servicio afectado, sin recrear los volúmenes:docker compose up -d --no-deps --force-recreate meilisearch3. Revisa los logs inmediatamente:
docker compose logs -f meilisearchEl flag
--no-depses esencial: reinicia el servicio objetivo sin tocar los otros contenedores (base de datos, caché, proxy). Sin él,docker compose up -dpuede recrear todo el stack.⚠️ Si la nueva versión migró el formato de los datos en disco (Meilisearch tras
--upgrade-db, Supabase PG17), revertir la imagen no es suficiente — por eso el backup del volumen es una precondición, no una opción.
Mantén la versión anterior disponible localmente
Antes de descargar la nueva imagen, etiqueta la imagen actualmente en producción con un nombre de retención:
docker tag getmeili/meilisearch:v1.53.0 getmeili/meilisearch:rollbackEsto te permite volver al estado exacto de producción en caso de emergencia, incluso sin acceso al registry o con conexión lenta. En un VPS con ancho de banda limitado, este tag local te ahorra varios minutos de descarga en el peor momento.
Los breaking changes recientes que derribaron instancias
Estos cuatro ejemplos ilustran por qué el protocolo anterior no es teórico.
Meilisearch (al subir de versión): un motor más reciente se niega a abrir una base escrita por una versión anterior, con Your database version (…) is incompatible with your current engine version, mientras no se pase --upgrade-db. Esa migración puede cambiar el formato en disco: en la v1.37.0 (marzo de 2026), los índices vectoriales aún en formato arroy se convirtieron al nuevo almacén HNSW (hannoy). Y la vuelta atrás no está soportada (Downgrade is not supported). La única salida limpia tras un fallo es haber exportado un dump antes de la actualización y luego importarlo (meilisearch --db-path /meili_data --import-dump /backup.dump) — algo posible solo si exportaste antes de actualizar.
Supabase Docker PostgreSQL 15 → 17 (PG 17 por defecto desde el 17 de junio de 2026): el contenedor supabase/postgres:17 no puede leer un volumen inicializado por PG 15. Supabase documenta explícitamente que el salto requiere un pg_upgrade mediante un script dedicado — el proceso de inicialización del contenedor no lo hace automáticamente. Sin una migración previa, la base de datos no arranca.
Langfuse v3 → v4 (v4.0.0 publicada el 29 de julio de 2026): la v4 abandona los endpoints de ingesta batch legacy en favor de OpenTelemetry. Los SDKs Python v2 y anteriores y los SDKs JS/TS v3 y anteriores son rechazados en la ingesta en cuanto el stack v4 funciona en su modo de escritura por defecto (events_only); los modos legacy y dual permiten escalonar la migración. Si tus servicios cliente no han migrado antes de la actualización del servidor, pierden silenciosamente todos sus trazados.
NocoDB (serie 2026.09): ninguna base rota, pero sí un cambio incompatible anunciado con antelación. Las notas de la 2026.09.0 recuerdan que las fechas devueltas por la API REST, los webhooks y las exportaciones pasarán de la precisión de segundo a la de milisegundo en PostgreSQL y SQLite en una próxima versión. Un cliente que lea las fechas con un formato estricto fallará sin que el servidor avise de nada — justo lo que la lectura de las notas de versión permite anticipar. Otra trampa, independiente de la versión: una instalación con bind-mounts (./postgres, ./nocodb) cuya ruta de montaje cambia entre dos despliegues arranca sobre una base vacía. Los volúmenes nombrados evitan esta deriva.
Estrategias de actualización: comparativa
Desplace la tabla
| Estrategia | Seguridad de datos | Tiempo de preparación | Rollback |
|---|---|---|---|
| `docker compose pull` + `up -d` directo | Sin garantía — breaking changes no detectados | < 1 minuto | Difícil si los datos fueron migrados |
| Bump de tag versionado + backup de volumen | Alta — datos guardados antes de cualquier cambio | 10 a 20 minutos | Trivial: revertir tag + `up -d --no-deps` |
| Test en staging antes de prod | Máxima — breaking changes detectados fuera de prod | Variable según el entorno | No necesario si el test pasó |
| Imagen fijada por digest SHA256 | Alta — inmune al tag-overwrite | Igual que tag versionado | Igual que tag versionado |
Integrar este protocolo en tu flujo de trabajo
Un protocolo que queda en una guía no sirve de nada. Para aplicarlo sistemáticamente, externaliza la versión en un archivo .env versionado en git:
# .env
MEILISEARCH_VERSION=v1.53.0
POSTGRES_VERSION=15.6# docker-compose.yml
services:
meilisearch:
image: getmeili/meilisearch:${MEILISEARCH_VERSION}Actualizar una versión se convierte entonces en un único commit sobre .env — legible en git log, reversible con git revert, y desplegable por CI/CD sin modificar el archivo Compose principal.
Para agencias que gestionan múltiples stacks de clientes, crea un archivo CHANGELOG_INFRA.md por cliente: cada actualización queda registrada con la versión anterior, la fecha, el backup realizado y el resultado. Esto también te protege contractualmente ante un eventual incidente posterior.
Automatizar sin perder el control
Si quieres ser notificado de nuevas versiones sin auto-pull, Diun (Docker Image Update Notifier) monitoriza tu registry y te envía una notificación (Slack, email, webhook) cuando hay una nueva imagen disponible. Tú decides cuándo actualizar.
Esta es la diferencia fundamental con Watchtower: Diun notifica, Watchtower actúa. En un stack de producción con volúmenes persistentes, la notificación es el nivel correcto de automatización — la acción sigue siendo manual y precedida del protocolo anterior.