Qué es MinIO y cuándo tiene sentido
MinIO es un servidor de almacenamiento de objetos escrito en Go, distribuido bajo licencia AGPLv3, que implementa el protocolo S3. Almacenamiento de objetos significa guardar archivos completos identificados por una clave dentro de un contenedor llamado bucket, en vez de un sistema de archivos con carpetas y permisos POSIX. Es el modelo que usan las imágenes de un catálogo, los adjuntos de un CRM, los respaldos o los artefactos de un pipeline.
Encaja bien en estos casos:
- Aplicaciones ya escritas contra S3 que quieres alojar sin factura de AWS.
- Adjuntos y subidas de usuario que no deben vivir en el disco del contenedor de la aplicación.
- Destino de respaldos de otras herramientas: Restic, Duplicati, Velero, n8n o Supabase Storage hablan S3.
- Entornos de desarrollo que necesitan un S3 real sin tocar producción.
No encaja si necesitas durabilidad de varias zonas geográficas sin construirla tú, o si vas a servir decenas de terabytes de tráfico público: ahí el ancho de banda del VPS y un CDN pesan más que el software.
Dimensionar el VPS antes de instalar
MinIO consume poca memoria en reposo (unos 200-400 MB) y el cuello de botella real es el disco y el ancho de banda. Para un uso normal de adjuntos y respaldos, 2 GB de RAM y 2 vCPU son suficientes; la cifra que debes calcular con cuidado es el espacio.
| Uso previsto | RAM sugerida | Disco | Comentario |
|---|---|---|---|
| Desarrollo y pruebas | 1-2 GB | 25-30 GB | Un solo disco, sin redundancia |
| Adjuntos de una app en producción | 2-4 GB | 80 GB NVMe | Deja 30 % libre para crecimiento |
| Destino de backups | 4 GB | 120 GB o más | El disco crece por retención, no por tráfico |
| Multimedia servida a usuarios | 4-8 GB | 150 GB + CDN | El ancho de banda manda sobre la RAM |
Dos advertencias honestas. Primera: con un solo disco no hay erasure coding, la técnica con la que MinIO reconstruye datos si falla una unidad. En un VPS de un solo volumen, la protección la aporta el backup externo, no MinIO. Segunda: MinIO no comprime ni deduplica. Un objeto de 1 GB ocupa 1 GB, más un pequeño coste de metadatos.
Comprueba el servidor antes de empezar:
free -h
df -h /
nproc Si dudas del tamaño, la guía sobre cuánta RAM necesita un VPS ayuda a sumar servicios en el mismo host.
Instalar MinIO con Docker Compose
El despliegue oficial usa la imagen quay.io/minio/minio, expone el puerto 9000 para la API S3 y el 9001 para la consola web. Parte de un VPS con Docker ya instalado; si no lo tienes, sigue primero Docker en un VPS.
Crea el directorio de datos y el archivo de configuración:
sudo mkdir -p /srv/minio/data
cd /srv/minio docker-compose.yml:
services:
minio:
image: quay.io/minio/minio:latest
container_name: minio
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
MINIO_SERVER_URL: https://s3.ejemplo.com
MINIO_BROWSER_REDIRECT_URL: https://consola.ejemplo.com
volumes:
- /srv/minio/data:/data
ports:
- "127.0.0.1:9000:9000"
- "127.0.0.1:9001:9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 30s
timeout: 10s
retries: 3 Genera credenciales raíz que no vas a reutilizar en ninguna aplicación:
umask 077
cat > /srv/minio/.env <<EOF
MINIO_ROOT_USER=$(openssl rand -hex 8)
MINIO_ROOT_PASSWORD=$(openssl rand -base64 32)
EOF
cat /srv/minio/.env
docker compose up -d
docker compose logs --tail=50 minio Fíjate en el detalle de ports: publicar en 127.0.0.1 deja MinIO accesible solo desde el propio servidor. Internet llegará a través de Nginx, no directamente al contenedor. Si omites el prefijo, Docker abre el puerto en todas las interfaces y salta por encima de reglas simples de UFW.
Publicar MinIO con HTTPS detrás de Nginx
MinIO necesita dos nombres distintos: uno para la API S3 y otro para la consola, porque el navegador y el SDK esperan comportamientos diferentes. Crea dos registros A, s3.ejemplo.com y consola.ejemplo.com, apuntando a la IP del VPS.
server {
listen 443 ssl;
server_name s3.ejemplo.com;
client_max_body_size 0;
proxy_buffering off;
proxy_request_buffering off;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 300;
proxy_http_version 1.1;
chunked_transfer_encoding off;
}
} client_max_body_size 0 quita el límite de subida de Nginx, que por defecto rechaza cuerpos mayores de 1 MB con un error 413. Es el fallo número uno al subir archivos a un MinIO recién instalado. Desactivar el buffering evita que Nginx escriba en disco cada archivo antes de reenviarlo.
Repite el bloque para consola.ejemplo.com apuntando a 127.0.0.1:9001 y emite los certificados:
sudo certbot --nginx -d s3.ejemplo.com -d consola.ejemplo.com
curl -I https://s3.ejemplo.com/minio/health/live Ese endpoint de salud responde 200 OK cuando el servidor está operativo y sirve para el monitoreo externo.
La consola comunitaria ya no administra: usa mc
Desde la versión RELEASE.2025-05-24, la consola web de la edición comunitaria de MinIO quedó reducida a un explorador de objetos. La gestión de usuarios, políticas, buckets, ciclo de vida y replicación se retiró de la interfaz gráfica y pasó a la edición comercial AIStor. Fue un cambio polémico y conviene saberlo antes de montar un flujo de trabajo alrededor de una pantalla que ya no existe.
La buena noticia: toda esa administración sigue disponible, gratis, desde mc, el cliente oficial de línea de comandos.
curl -sSL https://dl.min.io/client/mc/release/linux-amd64/mc \
-o /usr/local/bin/mc
sudo chmod +x /usr/local/bin/mc
mc alias set local https://s3.ejemplo.com "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
mc admin info local Si el flujo gráfico es innegociable para tu equipo, las opciones reales son AIStor de pago, un cliente S3 de escritorio como Cyberduck o S3 Browser apuntando a tu endpoint, o alternativas como Garage y SeaweedFS. No hay una versión comunitaria con consola de administración completa.
Crear buckets, usuarios y políticas de acceso mínimo
Nunca entregues las credenciales raíz a una aplicación: crea un usuario por servicio con una política limitada a su bucket. Si una clave se filtra, el daño queda contenido en un bucket en lugar de en todo el almacenamiento.
mc mb local/uploads
mc version enable local/uploads
cat > /tmp/uploads-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": ["arn:aws:s3:::uploads/*"]
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::uploads"]
}
]
}
EOF
mc admin policy create local uploads-rw /tmp/uploads-policy.json
mc admin user add local app-uploads "$(openssl rand -base64 24)"
mc admin policy attach local uploads-rw --user app-uploads mc version enable activa el versionado del bucket: una sobrescritura o un borrado accidental deja la versión anterior recuperable. Cuesta espacio, así que combínalo con una regla de expiración:
mc ilm rule add --expire-delete-marker --noncurrent-expire-days 30 local/uploads Si el bucket debe servir imágenes públicas, hazlo explícito en vez de dejarlo abierto por descuido:
mc anonymous set download local/publico Conectar la aplicación desde Laravel y Node.js
Cualquier SDK de S3 funciona con MinIO si defines el endpoint y activas el estilo de ruta. El estilo de ruta significa que la URL queda como https://s3.ejemplo.com/mi-bucket/archivo.jpg en lugar de https://mi-bucket.s3.ejemplo.com/archivo.jpg. Sin él, el SDK intenta resolver un subdominio por bucket y falla si no tienes DNS comodín.
En Laravel, sobre el disco s3 ya incluido en config/filesystems.php:
AWS_ACCESS_KEY_ID=app-uploads
AWS_SECRET_ACCESS_KEY=la_clave_generada
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=uploads
AWS_ENDPOINT=https://s3.ejemplo.com
AWS_USE_PATH_STYLE_ENDPOINT=true En Node.js con el SDK v3:
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({
endpoint: "https://s3.ejemplo.com",
region: "us-east-1",
forcePathStyle: true,
credentials: {
accessKeyId: process.env.MINIO_ACCESS_KEY,
secretAccessKey: process.env.MINIO_SECRET_KEY,
},
});
await s3.send(new PutObjectCommand({
Bucket: "uploads",
Key: "facturas/2026-09.pdf",
Body: buffer,
ContentType: "application/pdf",
})); region es obligatoria en el SDK aunque MinIO la ignore en una instalación de un solo nodo: cualquier valor válido sirve. Para subidas grandes desde el navegador, genera URLs prefirmadas y evita que el archivo pase por tu servidor de aplicación.
MinIO frente a S3 y otras opciones
| Criterio | MinIO en VPS | AWS S3 | Cloudflare R2 | Garage |
|---|---|---|---|---|
| Coste | Fijo, el del VPS | Por GB + salida | Por GB, salida gratuita | Fijo, el del VPS |
| Consola de administración | Solo mc en la edición libre | Completa | Completa | CLI y API |
| Durabilidad multizona | La construyes tú | Incluida | Incluida | Entre tus nodos |
| Compatibilidad S3 | Muy alta | Referencia | Alta | Alta, subconjunto |
| Licencia | AGPLv3 | Propietaria | Propietaria | AGPLv3 |
| Mejor para | Adjuntos y backups propios | Escala sin operación | Tráfico público alto | Clústeres pequeños distribuidos |
Reconocer lo ajeno da criterio: si sirves mucho tráfico público, la salida gratuita de R2 gana por coste; y si necesitas once nueves de durabilidad sin equipo de operaciones, S3 lo entrega y un VPS no.
Backups y replicación
mc mirror copia objetos y metadatos hacia otro destino, y es la base de un respaldo verificable. Programa el espejo hacia un segundo VPS, un disco de otro proveedor o un bucket de un tercero:
mc alias set remoto https://s3-backup.otroproveedor.com CLAVE SECRETO
mc mirror --overwrite --remove local/uploads remoto/uploads-backup Usa --remove con cuidado: propaga borrados. Para copias de seguridad reales conviene omitirlo y depurar aparte, de modo que un borrado accidental en origen no destruya la copia.
La replicación de bucket entre dos MinIO da continuidad, no protección contra errores humanos. Si alguien elimina un objeto, la réplica lo elimina también. Versionado más copia fría fuera del servidor es la combinación que sí protege. Revisa la guía de backups en VPS para encajar MinIO en tu política general.
Prueba la restauración de verdad: descarga un objeto en otro servidor y ábrelo. Un mc mirror que termina sin error no demuestra que el archivo sea legible.
Problemas frecuentes al instalar MinIO
| Síntoma | Causa habitual | Solución |
|---|---|---|
| Error 413 al subir | Límite de cuerpo en Nginx | client_max_body_size 0 |
SignatureDoesNotMatch | Reloj desincronizado o Host alterado | timedatectl status y proxy_set_header Host $http_host |
El SDK busca bucket.dominio | Falta el estilo de ruta | forcePathStyle: true o AWS_USE_PATH_STYLE_ENDPOINT=true |
| La consola redirige mal | MINIO_BROWSER_REDIRECT_URL sin definir | Declarar la URL pública de la consola |
| No hay pestaña de usuarios | Consola comunitaria recortada | Administrar con mc admin |
| Disco lleno sin explicación | Versiones antiguas retenidas | Regla de ciclo de vida con mc ilm |
Instalar MinIO son diez minutos de Docker; operarlo bien son las decisiones que vienen después: publicarlo solo detrás de HTTPS, dejar el puerto 9000 atado a 127.0.0.1, crear un usuario por aplicación con política mínima y aceptar que la administración vive en mc, no en la consola. Con un solo disco no tienes redundancia: el backup externo verificado es lo que convierte tu MinIO en almacenamiento en el que se puede confiar.
Terranode ofrece VPS KVM con disco NVMe en Ecuador desde $2,99 al mes, con planes de 4 GB de RAM y 40 GB de disco por $9 que sirven de sobra para auto-alojar MinIO junto a tu aplicación.
Hosting con LiteSpeed, NVMe y soporte 24/7 desde $3/mes.