Tutorial

Despliegue

Actualizar un stack Docker Compose en producción

Un `docker compose pull` seguido de `up -d` siempre funcionó — hasta el día en que dejó de funcionar. Las rupturas de compatibilidad de Meilisearch, Supabase (PostgreSQL 15→17) y Langfuse (v3→v4) lo recuerdan: una instancia estable, en producción desde hace meses, puede negarse a arrancar o rechazar sus datos entrantes tras una actualización sin preparación. Esta guía te da un protocolo reproducible: haz un backup antes de hacer pull, revisa las notas de versión antes de relanzar y vuelve a la imagen anterior en menos de dos minutos si algo falla.

10 min de lectura5 pasos
Contenido· Por qué `latest` es el verdadero culpable1/9
  1. 01Por qué `latest` es el verdadero culpable
  2. 02Qué cubre esta guía — y qué no
  3. 03Paso 1 — Fija todas tus imágenes
  4. 04Protocolo de actualización — los 5 pasos
  5. 05Mantén la versión anterior disponible localmente
  6. 06Los breaking changes recientes que derribaron instancias
  7. 07Estrategias de actualización: comparativa
  8. 08Integrar este protocolo en tu flujo de trabajo
  9. 09Automatizar sin perder el control

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 de depends_on y service_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.0

Una 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

  1. 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 un pg_upgrade manual — 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.

  2. 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 healthy antes de hacer el dump:

    docker compose exec -T db pg_dump -U postgres -Fc mydb > backup_$(date +%Y%m%d_%H%M%S).dump

    La opción -T desactiva 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_data

    Verifica 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.

  3. 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 meilisearch

    Si 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 meilisearch

    Espera a que el healthcheck alcance el estado healthy antes de validar. Un servicio que arranca pero no está healthy todavía no es un servicio listo.

  4. 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: 30s

    El campo start_period es crítico para servicios de arranque lento (bases de datos, motores de búsqueda): evita que Docker declare el contenedor unhealthy durante la fase de inicialización y desencadene un reinicio prematuro.

    Consulta docker-compose-depends-on-healthcheck para la configuración completa de service_healthy en PostgreSQL — el mismo principio aplica a cualquier servicio que necesite un período de calentamiento.

  5. 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 (o git revert si hiciste commit del bump).
    2. Reinicia únicamente el servicio afectado, sin recrear los volúmenes:

    docker compose up -d --no-deps --force-recreate meilisearch

    3. Revisa los logs inmediatamente:

    docker compose logs -f meilisearch

    El flag --no-deps es esencial: reinicia el servicio objetivo sin tocar los otros contenedores (base de datos, caché, proxy). Sin él, docker compose up -d puede 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:rollback

Esto 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

EstrategiaSeguridad de datosTiempo de preparaciónRollback
`docker compose pull` + `up -d` directoSin garantía — breaking changes no detectados< 1 minutoDifícil si los datos fueron migrados
Bump de tag versionado + backup de volumenAlta — datos guardados antes de cualquier cambio10 a 20 minutosTrivial: revertir tag + `up -d --no-deps`
Test en staging antes de prodMáxima — breaking changes detectados fuera de prodVariable según el entornoNo necesario si el test pasó
Imagen fijada por digest SHA256Alta — inmune al tag-overwriteIgual que tag versionadoIgual 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.

Un VPS con acceso root para aplicar este protocolo

Dumps completos, snapshots antes de actualizar, rollback a una imagen anterior: este protocolo requiere acceso root y almacenamiento local controlable. El hosting compartido no te da este nivel de control sobre los volúmenes Docker.