docker pull minio/minio ya no funciona: los repositorios de MinIO en Docker Hub desaparecieron en septiembre de 2026. Si montaste tu almacenamiento con la guía para instalar MinIO con Docker, puedes seguir tirando de la copia de quay.io por ahora, pero esa imagen no volverá a actualizarse. Esta guía explica cómo migrar de MinIO a Garage con Docker: levantar Garage v2.4.1 junto a MinIO, copiar los buckets con rclone, verificarlos y cambiar los clientes. Lo probé entero con 9996 objetos y cuento qué tardó, qué falló y qué se quedó por el camino.

Puntos clave

  • Docker Hub devuelve 404 para minio/minio y minio/mc (comprobado el 16 de septiembre de 2026). La fecha de la retirada, entre el 9 y el 12 de septiembre, sale de informes de terceros: MinIO no la ha anunciado.
  • quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z todavía se descarga, pero está congelada y no incluye el parche de la CVE-2025-62506, con una puntuación CVSS de 8,1.
  • Garage no tiene versionado, bloqueo de objetos, políticas de bucket ni etiquetas de objeto. Si tus copias dependen de alguna de esas funciones, esta migración no te sirve.
  • Con rclone 1.75.1, copiar 9996 objetos (1018,5 MiB) de MinIO a Garage tardó 41,99 s de mediana, y la verificación descargando los dos lados no encontró diferencias.
  • La última pasada de rclone sync no debe llevar --checksum: con esa opción, rclone dio por buena una copia de 20 MiB que había cambiado en MinIO.

¿Qué ha pasado con las imágenes de MinIO en Docker Hub?

Los repositorios minio/minio y minio/mc ya no existen en Docker Hub. El 16 de septiembre de 2026 la API de Docker Hub respondía 404 con object not found para los dos, y la descarga fallaba con cualquier etiqueta, también con la que fijaba la guía de instalación:

$ docker pull minio/minio:RELEASE.2025-09-07T16-13-09Z
Error response from daemon: pull access denied for minio/minio,
repository does not exist or may require 'docker login'

MinIO no ha publicado ningún aviso, así que la fecha viene de terceros. Un colaborador de dandi-cli sitúa la retirada[1] entre el 9 y el 12 de septiembre de 2026, con una ventana medida de 18:12 a 20:13 UTC del día 11. Una incidencia abierta en Milvus[2] el 14 de septiembre describe la misma rotura en su entorno de desarrollo.

El resto de la historia sí es verificable. El repositorio de MinIO en GitHub[3] figura como archivado desde el 25 de abril de 2026 (la incidencia de Milvus dice 13 de febrero, pero GitHub manda). Su README abre con "THIS REPOSITORY IS NO LONGER MAINTAINED" y explica que la edición comunitaria se distribuye solo como código fuente.

¿Basta con pasar a la imagen de quay.io?

Basta para que tu MinIO vuelva a arrancar hoy, no como destino. La copia de quay.io sigue disponible, y en ella la etiqueta RELEASE.2025-09-07T16-13-09Z y latest apuntan al mismo resumen (sha256:14cea493), fechado el 7 de septiembre de 2025. Tiene variante arm64 y aquí se descargó en 3,2 s. Si tu compose todavía usa el nombre de Docker Hub, cámbialo y recrea el servicio:

sed -i 's#image: minio/minio:#image: quay.io/minio/minio:#' \
  docker-compose.yml
docker compose pull minio && docker compose up -d minio

Esa imagen arrastra un fallo de seguridad conocido. La última versión publicada en GitHub, RELEASE.2025-10-15T17-29-55Z[4], corrige la CVE-2025-62506[5]: una cuenta de servicio con política restringida puede crearse otra con todos los permisos de su propietario. El aviso la califica como alta, con 8,1 sobre 10.

En quay.io no hay imagen de esa versión. Las notas lo dicen claro: "For container environments, please clone the source and build the latest container". Te quedan dos caminos: compilar MinIO tú mismo o salir de él.

Qué no tiene Garage y cuándo no te conviene

Garage es un servidor de almacenamiento de objetos compatible con la interfaz de programación (API) de Amazon S3, escrito en Rust y publicado con licencia AGPLv3. Lo desarrolla Deuxfleurs, un pequeño proveedor de servicios autoalojados que lo usa en producción desde 2020.

Garage cubre lectura, escritura, listados, subidas multiparte, URL prefirmadas y sitios web estáticos. Lo que falta está en su tabla de compatibilidad con S3[6]. Sobre las listas de control de acceso (ACL) y las políticas, dice: "Garage implements none of them, and has its own system instead, built around a per-access-key-per-bucket logic".

Las cinco primeras filas las probé con el cliente mc contra Garage v2.4.1; las dos últimas salen de la documentación:

Función que tenías en MinIO Garage v2.4.1 Lo que respondió
Versionado de objetos No 501 en PutBucketVersioning
Bloqueo de objetos y retención No 501 en PutObjectLockConfiguration
Políticas de bucket y descarga anónima No 501 en GetBucketPolicy
Etiquetas de objeto No 501 en GetObjectTagging
Administración con mc admin No 400 Invalid endpoint: metrics
Reglas de ciclo de vida Solo caducidad y limpieza de multiparte Según la documentación
Consola web No Línea de comandos y API de administración

Si tus copias de seguridad usan el bloqueo de objetos para ser inmutables, o alguna aplicación lee versiones anteriores de un objeto, quédate en un servidor que tenga esas funciones. Para guardar ficheros de aplicaciones, repositorios de restic, copias con rclone o sitios estáticos, Garage cubrió todo lo que probé.

Cómo he probado la migración

Monté MinIO RELEASE.2025-09-07T16-13-09Z (de quay.io) y Garage v2.4.1 en un proyecto de Compose desechable, dentro de un contenedor de desarrollo linux/arm64 con 18 núcleos. La máquina estaba compartida con otras cargas y la carga media osciló entre 22 y 39 durante las mediciones, así que toma los tiempos como referencia aproximada, no como banco de pruebas.

Con mc mirror cargué en MinIO un bucket app-data de 9996 objetos y 1018,5 MiB:

  • 6000 documentos JSON de 2 a 40 KB
  • 3990 ficheros aleatorios de 4 a 256 KB
  • 5 copias idénticas de un fichero de 20 MiB
  • 1 fichero de 300 MiB, que mc subió en 19 partes

Añadí un objeto con Cache-Control, un metadato propio y dos etiquetas, un bucket versionado con 6 versiones del mismo objeto, un bucket publico con descarga anónima y un repositorio de restic 0.19.1 con una instantánea. Para copiar usé rclone 1.75.1, la versión estable del 4 de septiembre de 2026. Para cargar los datos usé mc RELEASE.2025-08-13T08-35-41Z, la última imagen del cliente en quay.io.

Paso 1: levanta Garage junto a MinIO

Garage necesita un fichero de configuración, dos carpetas y dos secretos, y conviene que arranque en el mismo proyecto de Compose que MinIO para que rclone llegue a los dos por la misma red. Este es el garage.toml que usé:

metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "lmdb"
metadata_auto_snapshot_interval = "6h"
replication_factor = 1
rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"

[s3_api]
s3_region = "us-east-1"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.localhost"

[s3_web]
bind_addr = "[::]:3902"
root_domain = ".web.garage.localhost"
index = "index.html"

[admin]
api_bind_addr = "[::]:3903"

Hay dos diferencias con la guía de inicio de Garage[7]. La primera es la región, us-east-1 en lugar de garage, por un motivo que explico al cambiar los clientes. La segunda es la ruta de los datos, /var/lib/garage en lugar de /tmp, para que sobrevivan al contenedor. El valor replication_factor = 1 es el modo de un solo nodo, y la propia guía avisa de que ese despliegue "should not be used in production" porque no guarda ninguna copia redundante.

Los dos secretos van en el .env del proyecto, generados con openssl, como se explica en variables de entorno y secretos en Docker Compose:

cat >> .env <<EOF
GARAGE_RPC_SECRET=$(openssl rand -hex 32)
GARAGE_ADMIN_TOKEN=$(openssl rand -base64 32)
EOF

Después, añade el servicio a tu docker-compose.yml, debajo del de MinIO:

  garage:
    image: dxflrs/garage:v2.4.1
    command: ["/garage", "server", "--single-node"]
    restart: unless-stopped
    environment:
      GARAGE_RPC_SECRET: ${GARAGE_RPC_SECRET}
      GARAGE_ADMIN_TOKEN: ${GARAGE_ADMIN_TOKEN}
    ports:
      - "3900:3900"
      - "3902:3902"
      - "127.0.0.1:3903:3903"
    volumes:
      - ./garage.toml:/etc/garage.toml:ro
      - ./garage/meta:/var/lib/garage/meta
      - ./garage/data:/var/lib/garage/data

La opción --single-node, disponible desde la v2.3.0, crea la distribución del clúster en el primer arranque y la conserva en los siguientes. Las dos carpetas son montajes del anfitrión (lo explica el artículo sobre volúmenes y bind mounts en Docker): meta guarda la base de datos LMDB y data los bloques. La guía de despliegue de Garage[8] recuerda que los ficheros LMDB no se pueden mover entre arquitecturas distintas, así que no copies meta de un servidor x86 a uno arm64.

Arranca con docker compose up -d garage y comprueba el nodo con garage health, un subcomando que llegó con la v2.4.0[9] el 6 de septiembre de 2026. La v2.4.1 salió dos días después y solo corrige un fallo al arrancar con Consul o Kubernetes. La salida, recortada:

$ docker compose exec garage /garage health
Cluster health:            HEALTHY
Known nodes:               1
Storage nodes up:          1
Partitions:                256
Partitions with quorum:    256
Fully healthy partitions:  256

Paso 2: crea las claves y los buckets en Garage

Garage no tiene usuario raíz: cada clave de acceso recibe permisos bucket a bucket. Crea una clave para la migración y un bucket por cada bucket de MinIO, con el mismo nombre:

alias garage='docker compose exec -T garage /garage'
garage key create migracion
garage bucket create app-data
garage bucket allow --read --write --owner app-data --key migracion

garage key create imprime un identificador que empieza por GK y un secreto de 64 caracteres hexadecimales. Apúntalos: son los que usará rclone.

Si no quieres tocar las credenciales de cada aplicación el día del cambio, garage key import acepta también las claves de MinIO. Sin --yes se niega con este aviso: "This command is intended to re-import keys that were previously generated by Garage". Con --yes, la v2.4.1 aceptó una clave de MinIO sin el prefijo GK, y restic trabajó con ella sin problemas.

Esa importación tiene dos límites: el identificador necesita al menos 8 caracteres y el secreto al menos 16. Por eso el usuario admin de la guía de instalación no pasó, con el error Key identifiers should be at least 8 characters long. Tómalo como un puente para el día del cambio y rota esas claves después:

garage key import --yes -n app-antigua \
  clave_de_acceso_de_minio secreto_de_minio
garage bucket allow --read --write app-data --key app-antigua

Paso 3: copia los datos con rclone sync

rclone copia de un servidor S3 a otro sin pasar por el disco local, y la documentación de clientes de Garage[10] da una plantilla con provider = Other. Este rclone.conf declara los dos extremos con los nombres de servicio de Compose:

[minio]
type = s3
provider = Minio
access_key_id = usuario_raiz_de_minio
secret_access_key = clave_raiz_de_minio
endpoint = http://minio:9000

[garage]
type = s3
provider = Other
access_key_id = identificador_gk_de_migracion
secret_access_key = secreto_de_migracion
region = us-east-1
endpoint = http://garage:3900
force_path_style = true

La copia se ejecuta en un contenedor conectado a la red del proyecto. Compose la llama <carpeta>_default; en este ejemplo la carpeta es minio, y docker network ls te da el nombre real:

docker run --rm --network minio_default \
  -v "$PWD/rclone.conf:/config/rclone/rclone.conf:ro" \
  rclone/rclone:1.75.1 sync minio:app-data garage:app-data \
  --metadata --fast-list --transfers 8 --checkers 16 -v

Repetí la copia completa tres veces, con Garage vaciado entre una y otra. Tardó 53,42 s, 40,11 s y 41,99 s (unos 24 MiB/s en la mediana), con una carga media de entre 25,7 y 38,5 en 18 núcleos. Las tres terminaron con 9997 objetos copiados y ningún error. La opción --fast-list reduce las llamadas de listado, como aconseja la documentación de Garage, y --metadata conserva las cabeceras: el objeto de prueba llegó con Cache-Control: max-age=3600 y X-Amz-Meta-Autor: ana.

Mientras MinIO sigue en servicio, puedes repetir sync las veces que quieras. Sin cambios en el origen, una serie de tres pasadas tardó entre 2,43 y 2,52 s, y otra anterior entre 4,25 y 21,43 s, con la misma carga de fondo. Con 10 objetos modificados y 1 borrado en MinIO, la pasada sustituyó los 10, borró el sobrante en Garage y terminó en 6,63 s.

Paso 4: verifica la copia con rclone check

rclone check compara tamaño y suma MD5 de cada objeto, pero no todos los objetos tienen una suma comparable. La primera verificación dio este resultado:

NOTICE: S3 bucket app-data: 0 differences found
NOTICE: S3 bucket app-data: 6 hashes could not be checked
NOTICE: S3 bucket app-data: 9997 matching files

Los 6 objetos sin comprobar son los que mc subió por partes: las 5 copias de 20 MiB, con la cabecera ETag terminada en -2, y el fichero de 300 MiB, con -19. Ese ETag no es el MD5 del contenido, así que rclone solo puede comparar el tamaño. Los objetos que escribí después con mc pipe sumaron otros 10 a la lista.

Ese hueco lo comprobé a propósito. Sustituí en MinIO una de las copias de 20 MiB por otro fichero del mismo tamaño y lancé rclone sync --checksum: no copió nada, y rclone check volvió a decir 0 differences found. La pasada sin --checksum, que compara también la fecha de modificación, sí lo copió. Por eso la verificación que vale es la que descarga los dos lados:

docker run --rm --network minio_default \
  -v "$PWD/rclone.conf:/config/rclone/rclone.conf:ro" \
  rclone/rclone:1.75.1 check minio:app-data garage:app-data \
  --download --fast-list --checkers 16

Con 1018,5 MiB, la comprobación con --download[11] tardó 20,44 s de mediana en tres pasadas, con carga de entre 33 y 37. Las tres terminaron con 9996 matching files y ninguna diferencia.

Paso 5: cambia los clientes a Garage

Cada cliente necesita tres cambios: la dirección (puerto 3900 en lugar de 9000), las credenciales, si no importaste las de MinIO, y la región.

La región es la trampa que no avisa. MinIO sin región configurada aceptó firmas con us-east-1, eu-west-1 y garage, así que un cliente puede llevar años con cualquier valor. Garage solo acepta la suya. Con el s3_region = "garage" de la guía oficial, rclone configurado con us-east-1 recibió esto:

api error AuthorizationHeaderMalformed: Authorization header
malformed, unexpected scope: '20260916/us-east-1/s3/aws4_request',
expected: '20260916/garage/s3/aws4_request'

Revisa qué región usa cada cliente y pon esa en s3_region. En mi prueba era us-east-1: cambiarla en garage.toml y recrear el contenedor lo resolvió sin tocar el cliente, y después la región garage pasó a fallar igual. Los datos y la distribución del nodo sobrevivieron al cambio.

Para restic, que es el cliente con más que perder, copié también su bucket con rclone y apunté el repositorio a Garage con la clave importada de MinIO. Si todavía no lo usas, la guía para instalar restic para copias cifradas explica el resto:

export RESTIC_REPOSITORY=s3:http://garage:3900/restic
restic snapshots
restic check --read-data

restic snapshots mostró la instantánea de MinIO y check --read-data terminó con no errors were found. La siguiente copia, ya contra Garage, añadió 1,018 KiB al repositorio, porque el resto de los datos ya estaba allí. No hizo falta indicar la región: restic la detecta sola.

Con los clientes probados, el cambio definitivo sigue este orden:

  1. Para las aplicaciones que escriben en MinIO.
  2. Lanza el último rclone sync, sin --checksum.
  3. Ejecuta rclone check --download.
  4. Cambia la dirección y las credenciales de cada aplicación y arráncalas.
  5. Deja MinIO parado, sin borrar sus datos, hasta confirmar que todo funciona.

Qué se pierde al migrar de MinIO a Garage

rclone copia la versión actual de cada objeto con sus cabeceras, y nada más. Esto es lo que se quedó en MinIO en la prueba:

  • Versiones anteriores: el bucket versionado tenía 6 versiones de config.txt, y en Garage quedó 1, la última
  • Etiquetas de objeto: desaparecieron sin que rclone escribiera ningún aviso en su registro
  • Descarga anónima: el mismo logo.jpg que MinIO servía sin credenciales devolvió 403 en Garage
  • Usuarios y políticas de MinIO: no se copian; en Garage se sustituyen por claves con permisos por bucket

La descarga pública tiene sustituto. garage bucket website --allow publico activa el modo web del bucket, que Garage sirve en el puerto 3902 según el nombre de host. Con la cabecera Host: publico.web.garage.localhost, la misma petición devolvió 200 y los 153 711 bytes del fichero. En producción, ese nombre lo resuelve tu proxy inverso.

Cuánta memoria y disco usan MinIO y Garage

Garage ocupó menos memoria y menos disco con los mismos datos. Estas cifras salen de docker stats y du durante la prueba:

Medida MinIO 2025-09-07 Garage v2.4.1
Memoria en reposo, con los datos cargados 452 MiB 22,6 MiB
Pico de memoria durante cada copia 520, 473 y 461 MiB 66, 159 y 47 MiB
Disco de los cuatro buckets 1074 MiB 884 MiB (859 de bloques y 25 de metadatos)
Imagen arm64 comprimida 57,5 MB 27,0 MB

La diferencia de disco tiene dos causas documentadas. Garage parte cada objeto en bloques de 1 MiB y guarda una sola vez los bloques repetidos, así que las 5 copias de 20 MiB ocupan como una. Además comprime los bloques con zstd, con nivel 1 por defecto, y los 6000 documentos JSON se comprimen bien. Con datos ya comprimidos, como fotos o vídeo, la ventaja se reduce a la deduplicación.

Preguntas frecuentes

¿Puedo seguir con la imagen de MinIO de quay.io?

Puedes, pero sin actualizaciones y sin el parche de la CVE-2025-62506. Si usas cuentas de servicio o credenciales temporales con políticas restringidas, esa vulnerabilidad te afecta, y la única salida oficial es compilar RELEASE.2025-10-15T17-29-55Z tú mismo.

¿Sirve Garage para copias de seguridad inmutables?

No. Garage devuelve 501 a las llamadas de bloqueo de objetos y de versionado, así que no puede impedir que alguien con la clave borre o sobrescriba una copia. Para eso necesitas otro servidor, o copias cifradas en un segundo destino que el primero no pueda tocar.

¿Necesito más de un nodo de Garage?

No para que funcione: todo lo de esta guía se ejecutó en un solo nodo. Pero un nodo no guarda copias redundantes, y la documentación de Garage recomienda al menos tres nodos para el modo clúster, con las tres copias de cada dato en ubicaciones distintas. No probé un clúster, porque en una sola máquina no demuestra nada.

Conclusión

Migrar de MinIO a Garage con Docker son cinco pasos, y ninguno es largo. Garage arranca con un fichero de 20 líneas, rclone copia 1018,5 MiB en unos 42 s y rclone check --download confirma el resultado. Lo que decide si puedes migrar no es el procedimiento, sino las funciones que usas: sin versionado, bloqueo de objetos ni políticas, Garage no sirve para todos. Si tus buckets guardan ficheros de aplicaciones o repositorios de restic, haz primero la copia en paralelo, cuida la región y no uses --checksum en la última pasada.

La versión en inglés de esta guía está en How to migrate from MinIO to Garage with Docker.

Fuentes

  1. dandi-cli sitúa la retirada
  2. incidencia abierta en Milvus
  3. repositorio de MinIO en GitHub
  4. RELEASE.2025-10-15T17-29-55Z
  5. CVE-2025-62506
  6. tabla de compatibilidad con S3
  7. guía de inicio de Garage
  8. guía de despliegue de Garage
  9. v2.4.0
  10. documentación de clientes de Garage
  11. comprobación con --download
  12. Quay, etiqueta RELEASE.2025-09-07T16-13-09Z de minio/minio
  13. Garage, notas de la v2.4.1
  14. Garage, referencia de configuración