Garage y SeaweedFS: cómo desplegar tu propio almacenamiento S3 en un servidor dedicated
TL;DR
En esta guía prepararemos un servidor dedicated y ejecutaremos en él dos almacenamientos compatibles con S3: Garage para una instalación compacta y tolerante a fallos, y SeaweedFS para escenarios con una gran cantidad de objetos y escalado por nodos. El acceso a ambos servicios estará protegido con HTTPS, y las configuraciones y los datos estarán preparados para copias de seguridad.
- Garage es adecuado para un S3 privado sencillo, copias de seguridad y aplicaciones.
- SeaweedFS es más conveniente con una gran cantidad de archivos, alta carga y posterior escalado horizontal.
- Para un único nodo de prueba, bastan 4 vCPU, 8 GB de RAM y NVMe desde 500 GB.
- Para production, es mejor utilizar memoria ECC, RAID o ZFS, discos separados para los datos y una copia de seguridad fuera del servidor.
- La API S3 se publica mediante Caddy con certificados automáticos de Let’s Encrypt.
- Es necesario respaldar no solo las configuraciones, sino también los datos, las claves de acceso, el layout de Garage y los metadatos de SeaweedFS.
1. Qué configuramos y por qué
S3 no es un servidor específico, sino un protocolo de almacenamiento de objetos: las aplicaciones acceden a un bucket, cargan objetos y los obtienen mediante sus claves. La mayoría de los clientes, SDK y herramientas de copia de seguridad modernos pueden trabajar con una API compatible con S3. Por ello, en lugar de una nube pública, se puede ejecutar un almacenamiento propio en un servidor dedicated.
El artículo examina dos proyectos. Garage es un almacenamiento S3 distribuido compacto, orientado a una operación sencilla y clústeres pequeños. SeaweedFS es un sistema de archivos distribuido más versátil con gateway S3, servicio master, servidores volume y filer.
Por lo general, no es necesario usar Garage y SeaweedFS simultáneamente para los mismos datos. Elige un backend principal:
- Garage: cuando necesitas un S3 privado claro, un número mínimo de componentes y varios nodos en el futuro.
- SeaweedFS: cuando se esperan millones o miles de millones de objetos pequeños, alta velocidad de escritura y evolución del clúster.
- Ambos en un dedicated: solo para laboratorio, migración, comparación o alojamiento de tareas independientes diferentes.
Qué obtendrás como resultado
Después de seguir las instrucciones, habrá dos endpoint S3 independientes funcionando en el servidor:
https://garage.example.com: API S3 de Garage;https://seaweed.example.com: API S3 de SeaweedFS;https://garage-admin.example.com: interfaz administrativa de Garage, si fuera necesaria;- directorios locales con objetos en un disco independiente;
- archivos Docker Compose, gestión mediante systemd, firewall y esquema de copia de seguridad.
En los ejemplos se utilizan los dominios garage.example.com y seaweed.example.com. Sustitúyelos por tus propios dominios. Los registros DNS deben apuntar a la dirección IPv4 pública del servidor, y para IPv6 debes comprobar por separado que el firewall y el enrutamiento estén configurados correctamente.
Self-hosted o S3 managed
Managed S3 libera al propietario del servidor de las tareas de operación: redundancia, sustitución de discos, monitorización y ampliación. Es una buena elección si la rapidez de lanzamiento es más importante que el control sobre los datos. Sin embargo, el precio del almacenamiento, el tráfico saliente y una gran cantidad de operaciones puede aumentar considerablemente.
Self-hosted S3 permite controlar los discos, las claves, la ubicación y el coste de almacenamiento. La contrapartida es que la responsabilidad de las copias de seguridad, la tolerancia a fallos y las actualizaciones recae totalmente en el administrador. Un servidor sin una copia externa no es un almacenamiento fiable, aunque el propio software admita replicación.
| Criterio | Managed S3 | Garage o SeaweedFS |
|---|---|---|
| Puesta en marcha | Muy rápida | Requiere configurar el servidor |
| Control sobre los datos | Limitado por la política del proveedor | Control total |
| Escalado | Normalmente automático | Debe planificarse de forma independiente |
| Responsabilidad de los backups | Compartida con el proveedor | Del propietario de la infraestructura |
2. Qué configuración de VPS se necesita para esta tarea
Los requisitos dependen del volumen de datos, el tamaño de los objetos y el número de solicitudes. El almacenamiento S3 depende principalmente del subsistema de discos y de la red, no de la frecuencia de la CPU. Para una gran cantidad de archivos pequeños, SeaweedFS consume más RAM para metadatos y operaciones en segundo plano que un clúster Garage sencillo.
| Escenario | CPU | RAM | Disco | Red |
|---|---|---|---|---|
| Laboratorio y pruebas | 2 vCPU | 4 GB | 100 GB SSD | 100 Mbit/s |
| Production pequeño | 4 vCPU | 8–16 GB | 500 GB–2 TB NVMe | 1 Gbit/s |
| Muchos objetos y API activa | 8–16 vCPU | 32–64 GB | 2–8 TB NVMe o storage independiente | 1–10 Gbit/s |
Opción inicial práctica
Para un único nodo de production en el que se ejecuten simultáneamente Garage, SeaweedFS y Caddy, una configuración inicial razonable es 8 vCPU, 16 GB de RAM, 1 TB NVMe, IPv4 pública y un puerto de 1 Gbit/s. Si se prevé utilizar solo un backend, se puede empezar con 4 vCPU y 8 GB de RAM.
Como una de las opciones, puedes elegir un dedicated con estas características. El plan específico no forma parte de la arquitectura: son importantes el rendimiento de los discos, el acceso a copias de seguridad, el límite de tráfico y la posibilidad de añadir un segundo disco.
Por qué un dedicated puede ser mejor que un VPS
Un dedicated es preferible si los datos ocupan cientos de gigabytes o terabytes, se requiere un rendimiento de I/O predecible y se prevé escritura continua. En un VPS, la velocidad de los discos puede depender de otros usuarios, y el volumen de disco garantizado a veces está limitado por el plan.
Para un bucket pequeño, copias de seguridad y development, un VPS es completamente suficiente. Un dedicated se justifica con un alto número de operaciones, grandes cargas secuenciales, requisitos de memoria ECC, RAID/ZFS o la necesidad de controlar físicamente los discos.
Discos y sistema de archivos
Para object storage, utiliza NVMe local o SSD enterprise. No coloques los datos en la partición del sistema sin un límite independiente: llenar el root filesystem puede detener SSH, Docker y los servicios del sistema. Un esquema práctico es un volumen independiente montado en /srv/object-storage.
Para un solo nodo, XFS o ext4 son adecuados. ZFS resulta útil con suficiente RAM y necesidad de snapshot, checksumming y gestión de pools, pero añade complejidad operativa. RAID protege contra el fallo de un disco, pero no sustituye una copia de seguridad.
Ubicación del servidor
Elige un centro de datos más cercano a las aplicaciones y los usuarios. Esto reduce la latencia al cargar objetos y disminuye el tiempo de multipart upload. Si el almacenamiento se utiliza solo para backups, son más importantes un tráfico saliente estable y la posibilidad de transferir datos a otra ubicación geográfica.
Para datos personales, considera también los requisitos legales, los contratos con los clientes y la jurisdicción física del centro de datos. La proximidad geográfica por sí sola no garantiza el cumplimiento de los requisitos de procesamiento de datos.
3. Preparación del servidor
A continuación se asume Debian 12 o Ubuntu Server 24.04 LTS con una instalación limpia y un usuario con acceso temporal mediante SSH. Todos los comandos se ejecutan como un usuario con sudo. En los ejemplos se utiliza el nombre storage.
Actualización del sistema y paquetes básicos
Primero actualiza el índice de paquetes, instala las correcciones de seguridad y las herramientas que necesitarás para Docker, diagnóstico y copias de seguridad.
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release jq vim htop ncdu \
unzip rsync restic ufw fail2ban dnsutils smartmontools
Creación de un administrador independiente
No uses root para el trabajo diario. Crea un usuario, añádelo a sudo y transfiere la clave pública SSH. Sustituye el comando con la clave por tu propia clave.
sudo adduser storage
sudo usermod -aG sudo storage
sudo install -d -m 700 -o storage -g storage /home/storage/.ssh
sudo sh -c 'echo "ssh-ed25519 AAAA_REPLACE_WITH_YOUR_PUBLIC_KEY admin@workstation" > /home/storage/.ssh/authorized_keys'
sudo chown storage:storage /home/storage/.ssh/authorized_keys
sudo chmod 600 /home/storage/.ssh/authorized_keys
Abre una nueva sesión SSH y asegúrate de que el acceso funciona. Solo después de comprobarlo se puede desactivar la autenticación por contraseña y el login de root.
ssh storage@SERVER_IP
sudo install -d -m 0755 /etc/ssh/sshd_config.d
sudo tee /etc/ssh/sshd_config.d/ hardening.conf > /dev/null <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
EOF
sudo sshd -t
sudo systemctl reload ssh
En el último comando, en una terminal real, el nombre de archivo debe ir sin espacio: /etc/ssh/sshd_config.d/hardening.conf. Versión corregida:
sudo tee /etc/ssh/sshd_config.d/hardening.conf > /dev/null <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
EOF
sudo sshd -t
sudo systemctl reload ssh
Firewall
Antes de activar UFW, permite SSH en un puerto no estándar si lo has cambiado, así como HTTP y HTTPS para la verificación ACME y la API S3. No publicamos hacia el exterior los puertos administrativos de Garage y SeaweedFS.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Fail2ban y directorio de datos
Fail2ban bloqueará temporalmente las direcciones con un gran número de intentos fallidos de acceso SSH. No sustituye las claves, el firewall ni las actualizaciones, pero reduce el ruido de los escáneres automatizados.
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban --no-pager
sudo mkdir -p /srv/object-storage/{garage,seaweedfs,caddy,data,backup}
sudo chown -R storage:storage /srv/object-storage
Comprueba el disco antes del despliegue:
df -hT
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,MODEL
sudo smartctl -a /dev/nvme0n1 | less
Si se utiliza un disco independiente para los datos, primero crea el sistema de archivos, añade el UUID a /etc/fstab, móntalo en /srv/object-storage y solo entonces inicia los contenedores. No formatees el disco hasta haber comprobado su dispositivo mediante lsblk.
4. Instalación del software
Para aislar los componentes utilizamos Docker Engine y Compose plugin. En production es mejor fijar las versiones de las imágenes en lugar de usar la etiqueta latest. Los ejemplos incluyen versiones vigentes para una implementación práctica en 2026; antes de actualizar, revise las release notes y la compatibilidad del formato de datos.
Instalación de Docker Engine desde el repositorio oficial
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
Añada el repositorio de Docker para Ubuntu 24.04. Para Debian, sustituya la ruta ubuntu por debian y el codename por bookworm.
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker "$USER"
Reinicie la sesión SSH o ejecute newgrp docker; después, compruebe la instalación:
docker run --rm hello-world
docker compose version
sudo systemctl enable --now docker
Versiones de las imágenes
El ejemplo utiliza Garage 2.1.0 y SeaweedFS 3.99. Si para el momento de la instalación se han publicado versiones estables más recientes, pruébelas primero en una copia de los datos. No se debe cambiar sin más a una imagen con otra major-versión: en los sistemas de almacenamiento, la migración de metadatos puede ser irreversible.
Cree el directorio de trabajo y el archivo Compose:
mkdir -p /srv/object-storage/app
cd /srv/object-storage/app
touch .env docker-compose.yml garage.toml s3.json Caddyfile
chmod 600 .env
Creación de secretos
Los secretos no deben almacenarse en un repositorio Git público, Dockerfile ni en el historial de comandos. Genere el token administrativo, access key y secret key mediante el generador de datos aleatorios del sistema.
GARAGE_ADMIN_TOKEN=$(openssl rand -hex 32)
S3_ACCESS_KEY=$(openssl rand -hex 16)
S3_SECRET_KEY=$(openssl rand -hex 32)
sudo tee /srv/object-storage/app/.env > /dev/null <<EOF
GARAGE_ADMIN_TOKEN=${GARAGE_ADMIN_TOKEN}
S3_ACCESS_KEY=${S3_ACCESS_KEY}
S3_SECRET_KEY=${S3_SECRET_KEY}
EOF
sudo chmod 600 /srv/object-storage/app/.env
Guarde estos valores en un gestor de contraseñas. La pérdida de la access key no destruirá los datos, pero requerirá crear una nueva clave. La pérdida de los secretos administrativos complicará la recuperación del acceso.
5. Configuración de Garage y SeaweedFS
Configuración de Garage
Garage utiliza un archivo de configuración TOML y un directorio de metadatos independiente. Para un nodo configuraremos RPC local, S3 API en el puerto 3900 y web endpoint en el puerto 3902. En un clúster de production, el puerto RPC debe estar disponible únicamente entre los nodos y no desde Internet.
cat > /srv/object-storage/app/garage.toml <<'EOF'
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "lmdb"
replication_factor = 1
consistency_mode = "consistent"
[rpc]
bind_addr = "[::]:3901"
secret_file = "/run/secrets/garage_rpc_secret"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.local"
[admin]
api_bind_addr = "[::]:3903"
admin_token = "CHANGE_IN_ENV_OR_SECRET"
[web]
bind_addr = "[::]:3902"
root_domain = ".web.garage.local"
index = "index.html"
EOF
Para production, no deje la línea con el token de demostración. Según la versión de Garage, la sintaxis de la sección admin puede variar, por lo que antes de iniciar revise el ejemplo de configuración en las release notes. A continuación se utiliza un esquema más seguro: la API administrativa no se publica externamente y el servicio Docker recibe el token mediante una variable de entorno solo para operaciones auxiliares.
Cree el secreto RPC y la estructura de directorios:
openssl rand -hex 32 > /srv/object-storage/app/garage-rpc-secret
chmod 600 /srv/object-storage/app/garage-rpc-secret
mkdir -p /srv/object-storage/data/garage/{meta,data}
chown -R 1000:1000 /srv/object-storage/data/garage
Configuración de SeaweedFS
En una sola máquina, SeaweedFS puede ejecutarse en modo master, volume, filer y S3 gateway. Este esquema es conveniente para pruebas y production de pequeña escala, pero no proporciona tolerancia a fallos: un fallo del servidor implica la indisponibilidad de todo el almacenamiento.
cat > /srv/object-storage/app/s3.json <<'EOF'
{
"identities": [
{
"name": "admin",
"credentials": [
{
"accessKey": "CHANGE_ACCESS_KEY",
"secretKey": "CHANGE_SECRET_KEY"
}
],
"actions": ["Read", "Write", "List", "Tagging"]
}
]
}
EOF
chmod 600 /srv/object-storage/app/s3.json
Sustituya los valores de .env en lugar de CHANGE_ACCESS_KEY y CHANGE_SECRET_KEY. Sustitución automática sin mostrar el secreto en los argumentos del proceso:
set -a
. /srv/object-storage/app/.env
set +a
sed -i "s/CHANGE_ACCESS_KEY/${S3_ACCESS_KEY}/; s/CHANGE_SECRET_KEY/${S3_SECRET_KEY}/" \
/srv/object-storage/app/s3.json
Docker Compose
Cree un único archivo Compose. Los servicios Garage y SeaweedFS utilizan distintos puertos y directorios. Para SeaweedFS se utiliza la imagen chrislusf/seaweedfs:3.99. Si la etiqueta oficial ha cambiado, sustitúyala por una versión estable verificada.
services:
garage:
image: dxflrs/garage:v2.1.0
container_name: garage
restart: unless-stopped
command: ["/garage", "-c", "/etc/garage.toml", "server"]
volumes:
- /srv/object-storage/app/garage.toml:/etc/garage.toml:ro
- /srv/object-storage/app/garage-rpc-secret:/run/secrets/garage_rpc_secret:ro
- /srv/object-storage/data/garage/meta:/var/lib/garage/meta
- /srv/object-storage/data/garage/data:/var/lib/garage/data
ports:
- "127.0.0.1:3900:3900"
- "127.0.0.1:3902:3902"
- "127.0.0.1:3903:3903"
- "127.0.0.1:3901:3901"
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:3903/health"]
interval: 30s
timeout: 5s
retries: 5
seaweed-master:
image: chrislusf/seaweedfs:3.99
container_name: seaweed-master
restart: unless-stopped
command: master -ip=seaweed-master -mdir=/data
volumes:
- /srv/object-storage/data/seaweedfs/master:/data
expose:
- "9333"
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:9333/cluster/status"]
interval: 30s
timeout: 5s
retries: 5
seaweed-volume:
image: chrislusfs/seaweedfs:3.99
container_name: seaweed-volume
restart: unless-stopped
command: volume -mserver=seaweed-master:9333 -dir=/data -port=8080
depends_on:
seaweed-master:
condition: service_healthy
volumes:
- /srv/object-storage/data/seaweedfs/volume:/data
expose:
- "8080"
seaweed-filer:
image: chrislusfs/seaweedfs:3.99
container_name: seaweed-filer
restart: unless-stopped
command: filer -master=seaweed-master:9333 -ip=seaweed-filer
depends_on:
seaweed-master:
condition: service_healthy
seaweed-volume:
condition: service_started
volumes:
- /srv/object-storage/data/seaweedfs/filer:/data
expose:
- "8888"
seaweed-s3:
image: chrislusfs/seaweedfs:3.99
container_name: seaweed-s3
restart: unless-stopped
command: s3 -filer=seaweed-filer:8888 -port=8333 -config=/etc/seaweed/s3.json
depends_on:
- seaweed-filer
volumes:
- /srv/object-storage/app/s3.json:/etc/seaweed/s3.json:ro
ports:
- "127.0.0.1:8333:8333"
caddy:
image: caddy:2.9
container_name: caddy
restart: unless-stopped
depends_on:
- garage
- seaweed-s3
ports:
- "80:80"
- "443:443"
volumes:
- /srv/object-storage/app/Caddyfile:/etc/caddy/Caddyfile:ro
- /srv/object-storage/data/caddy/data:/data
- /srv/object-storage/data/caddy/config:/config
Compruebe la sintaxis e inicie los servicios:
cd /srv/object-storage/app
docker compose --env-file .env config
docker compose --env-file .env up -d
docker compose ps
docker compose logs --tail=100 garage
docker compose logs --tail=100 seaweed-master seaweed-s3
Inicialización del layout de Garage
Después de iniciar Garage, debe obtener el identificador del nodo y asignarle capacity. Los comandos dependen de la versión de CLI y pueden ejecutarse dentro del contenedor.
cd /srv/object-storage/app
docker compose exec garage garage status
docker compose exec garage garage layout assign \
-z dc1 -c 800G NODE_ID
docker compose exec garage garage layout show
docker compose exec garage garage layout apply --version 1
En lugar de NODE_ID, introduzca el identificador de la salida de garage status. El valor 800G debe ser menor que el espacio libre en la partición. Para un clúster de tres nodos, utilice zonas diferentes, por ejemplo dc1, dc2, dc3, y replication factor 3.
La creación del bucket y de la clave también depende de la versión de CLI. La secuencia general es la siguiente:
docker compose exec garage garage bucket create backups
docker compose exec garage garage key create app-backups
docker compose exec garage garage bucket allow \
--read --write --owner backups --key app-backups
docker compose exec garage garage bucket list
docker compose exec garage garage key list
No otorgue permisos de owner a las aplicaciones si no es necesario. Para la aplicación, cree una clave independiente y limite el acceso a buckets específicos. Guarde la clave administrativa por separado de las claves de tareas automatizadas.
6. TLS, DNS y comprobación del funcionamiento
DNS
Cree registros A:
garage.example.com→ IPv4 del servidor;seaweed.example.com→ IPv4 del servidor.
Si se utiliza IPv6, añada registros AAAA solo después de comprobar la disponibilidad del servidor mediante IPv6. Un registro AAAA incorrecto puede hacer que parte de los clientes se conecten a una dirección no disponible.
dig +short garage.example.com
dig +short seaweed.example.com
Caddy y HTTPS
Caddy obtiene automáticamente certificados de Let’s Encrypt si el dominio ya apunta al servidor y los puertos 80/443 están disponibles desde Internet. Dentro de Garage y SeaweedFS se transmite tráfico HTTP normal a través de la red local de Docker; hacia el exterior solo se expone HTTPS.
cat > /srv/object-storage/app/Caddyfile <<'EOF'
garage.example.com {
reverse_proxy host.docker.internal:3900
request_body {
max_size 50GB
}
}
seaweed.example.com {
reverse_proxy seaweed-s3:8333
request_body {
max_size 50GB
}
}
EOF
En Linux, el contenedor de Caddy no siempre puede acceder a host.docker.internal sin una configuración especial. Es más fiable añadir Garage a una red Docker común y hacer proxy mediante el nombre del servicio. Para ello, cambie Caddyfile y el archivo Compose de modo que Caddy y Garage se encuentren en la misma red:
networks:
storage_net:
services:
garage:
networks:
- storage_net
seaweed-s3:
networks:
- storage_net
caddy:
networks:
- storage_net
Después de añadir la red, utilice los nombres de los contenedores:
cat > /srv/object-storage/app/Caddyfile <<'EOF'
garage.example.com {
reverse_proxy garage:3900
request_body {
max_size 50GB
}
}
seaweed.example.com {
reverse_proxy seaweed-s3:8333
request_body {
max_size 50GB
}
}
EOF
cd /srv/object-storage/app
docker compose up -d
docker compose logs --tail=100 caddy
El parámetro max_size no limita el tamaño real del bucket, sino que protege el reverse proxy frente a una solicitud accidentalmente enorme. Para archivos de más de 50 GB, utilice multipart upload o aumente el límite teniendo en cuenta el disco y los tiempos de espera.
Comprobación de HTTP y S3
curl -I https://garage.example.com
curl -I https://seaweed.example.com
curl -vk https://garage.example.com 2>&1 | grep -E "subject:|issuer:|HTTP/"
docker compose ps
docker stats --no-stream
Para el cliente S3, instale AWS CLI v2 desde el archivo oficial de AWS o utilice un cliente compatible con S3. Ejemplo con AWS CLI:
export AWS_ACCESS_KEY_ID='REPLACE_ACCESS_KEY'
export AWS_SECRET_ACCESS_KEY='REPLACE_SECRET_KEY'
export AWS_DEFAULT_REGION='garage'
aws --endpoint-url https://garage.example.com s3 ls
aws --endpoint-url https://garage.example.com s3 mb s3://test-bucket
printf 's3 smoke test\n' > /tmp/test.txt
aws --endpoint-url https://garage.example.com s3 cp /tmp/test.txt s3://test-bucket/test.txt
aws --endpoint-url https://garage.example.com s3 cp s3://test-bucket/test.txt -
aws --endpoint-url https://garage.example.com s3 rb s3://test-bucket --force
Para SeaweedFS, sustituya el endpoint:
aws --endpoint-url https://seaweed.example.com s3 mb s3://seaweed-test
aws --endpoint-url https://seaweed.example.com s3 cp /tmp/test.txt s3://seaweed-test/test.txt
aws --endpoint-url https://seaweed.example.com s3 ls s3://seaweed-test/
Si HTTPS aún no está configurado, compruebe temporalmente el endpoint local mediante http://127.0.0.1:8333. No deje el endpoint local de pruebas publicado en una interfaz pública ni desactive la comprobación de TLS en clientes de production.
7. Copias de seguridad y mantenimiento
Qué es necesario respaldar
El almacenamiento de objetos contiene varios tipos de datos:
- objetos de los usuarios en los directorios de Garage y SeaweedFS;
- metadatos de Garage, su layout y configuración;
- master data y filer metadata de SeaweedFS;
- Docker Compose, Caddyfile, configuración JSON y secretos;
- lista de buckets, usuarios, access key y políticas de acceso.
Copiar únicamente Docker Compose no restaurará los archivos. Copiar solo los datos de los volúmenes sin las configuraciones complicará el arranque. Antes de realizar la copia de seguridad, prepare un documento con las versiones de las imágenes, los puntos de montaje y los comandos de restauración.
Restic en un servidor independiente
No almacene la única copia de seguridad en el mismo disco. Una buena opción mínima es un servidor independiente mediante SFTP, un S3 remoto con object lock u otro centro de datos. A continuación se muestra un ejemplo de restic mediante SFTP. En el servidor de backup debe existir el usuario restic y el directorio del repositorio.
Cree un archivo con secretos accesible únicamente para root:
sudo tee /root/.restic-env > /dev/null <<'EOF'
export RESTIC_REPOSITORY='sftp:[email protected]:/srv/restic/object-storage'
export RESTIC_PASSWORD='CHANGE_TO_LONG_RANDOM_PASSWORD'
export RESTIC_SFTP_COMMAND='ssh -i /root/.ssh/restic_backup_ed25519 -o StrictHostKeyChecking=yes'
EOF
sudo chmod 600 /root/.restic-env
Inicialice el repositorio una vez y pruebe la conexión:
sudo bash -c 'source /root/.restic-env && restic init'
sudo bash -c 'source /root/.restic-env && restic snapshots'
Script de backup
Para obtener una copia coherente, detendremos los contenedores durante una breve ventana de mantenimiento. Para un solo nodo, esto es más sencillo y seguro que copiar archivos de volúmenes que cambian activamente. Para almacenes grandes, es preferible utilizar la replicación integrada, un snapshot del sistema de archivos o un clúster de backup independiente.
sudo tee /usr/local/sbin/object-storage-backup > /dev/null <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
source /root/.restic-env
APP=/srv/object-storage/app
DATA=/srv/object-storage/data
STAMP=$(date +%F-%H%M)
LOG=/var/log/object-storage-backup.log
exec > >(tee -a "$LOG") 2>&1
echo "[$(date -Is)] backup started"
cd "$APP"
docker compose stop garage seaweed-master seaweed-volume seaweed-filer seaweed-s3
restic backup \
"$APP/garage.toml" \
"$APP/garage-rpc-secret" \
"$APP/s3.json" \
"$APP/Caddyfile" \
"$APP/docker-compose.yml" \
"$APP/.env" \
"$DATA/garage" \
"$DATA/seaweedfs" \
--tag object-storage \
--tag "$STAMP"
docker compose start garage seaweed-master seaweed-volume seaweed-filer seaweed-s3
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check
echo "[$(date -Is)] backup completed"
EOF
sudo chmod 700 /usr/local/sbin/object-storage-backup
El archivo .env contiene secretos, por lo que se incluye en la copia de seguridad únicamente si el acceso al repositorio remoto está estrictamente limitado. Si la política prohíbe almacenar secretos en la copia de seguridad, exclúyalo y guarde los secretos en un gestor cifrado independiente.
Cron y comprobación de la restauración
sudo crontab -e
Añada una ejecución cada noche a las 03:30:
30 3 * /usr/local/sbin/object-storage-backup
Una vez al mes, compruebe no solo la existencia del snapshot, sino también la restauración de un objeto independiente en un directorio temporal:
sudo bash -c 'source /root/.restic-env && restic snapshots --tag object-storage'
sudo mkdir -p /srv/restore-test
sudo bash -c 'source /root/.restic-env && restic restore latest --target /srv/restore-test --tag object-storage'
sudo find /srv/restore-test -maxdepth 4 -type f | head
Una copia de seguridad que nunca se ha restaurado es una suposición, no una prueba de recuperación. Compruebe los archivos de control, el tamaño de los directorios, los permisos de acceso y la posibilidad de iniciar los contenedores con la configuración restaurada.
Actualizaciones
Antes de actualizar, registre las imágenes actuales:
cd /srv/object-storage/app
docker compose images
docker image ls
sudo bash -c 'source /root/.restic-env && restic backup /srv/object-storage/app --tag pre-upgrade'
Para un solo nodo, utilice una maintenance window: detenga las escrituras, haga una copia de seguridad, descargue la nueva imagen y reinicie los servicios.
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=200 garage seaweed-master seaweed-s3
El rolling update solo es posible en un clúster de varios nodos con replicación y un protocolo compatible. Primero se actualiza un nodo, se comprueban el healthcheck y las operaciones de lectura/escritura, y después se pasa al siguiente. No actualice todos los nodos simultáneamente.
Monitorización
Como mínimo, controle el espacio libre, los inode, la RAM, el load average, SMART y los errores de los contenedores:
df -h /srv/object-storage
df -ih /srv/object-storage
free -h
uptime
docker compose ps
docker compose logs --since=15m garage seaweed-s3
sudo journalctl -u docker --since "15 minutes ago"
Configure una notificación cuando el disco supere el 80–85 por ciento de ocupación. Al alcanzar el 95 por ciento, los servicios pueden empezar a devolver errores de escritura y las operaciones de recuperación en segundo plano pueden finalizar de forma inesperada. Planifique la ampliación con antelación.
8. Resolución de problemas y preguntas frecuentes
¿Por qué Caddy devuelve 502 Bad Gateway?
Primero compruebe el estado de los contenedores con el comando docker compose ps y los registros con docker compose logs caddy garage seaweed-s3. Si Caddy no ve el nombre del servicio, asegúrese de que ambos contenedores estén conectados a la misma red Docker. Si se utiliza host.docker.internal, sustitúyalo por el nombre del servicio, por ejemplo garage:3900. También compruebe el endpoint local mediante curl http://127.0.0.1:3900 o una solicitud de red desde el contenedor de Caddy.
El certificado de Let’s Encrypt no se emite. ¿Qué comprobar?
Compruebe los registros A con el comando dig, la disponibilidad de los puertos 80 y 443 desde una red externa y la ausencia de otro reverse proxy. El dominio no debe tener un registro AAAA incorrecto habilitado. Consulte docker compose logs caddy. Si el DNS acaba de cambiar, espere a que se actualice el TTL. No realice muchas solicitudes repetidas ante un error: Let’s Encrypt aplica rate limits.
Error AccessDenied al cargar en S3
Compruebe a qué endpoint y bucket está conectado el cliente, así como los valores de las variables AWS_ACCESS_KEY_ID y AWS_SECRET_ACCESS_KEY. En Garage, la clave debe tener permisos para el bucket específico. En SeaweedFS, compruebe el formato de s3.json y la coincidencia de las claves. Para el diagnóstico, ejecute aws s3 ls sin multipart y compruebe que la hora del sistema esté sincronizada mediante NTP.
La escritura termina con el error No space left on device
Compruebe no solo los gigabytes libres mediante df -h, sino también los inode mediante df -i. Millones de archivos pequeños pueden agotar los inode antes que el espacio. Elimine imágenes antiguas de Docker solo después de comprobar que no son necesarias para rollback: docker system df. No elimine manualmente archivos dentro de los directorios de Garage o SeaweedFS: esto puede dañar los metadatos y provocar la pérdida de objetos.
¿Qué configuración de VPS es mínimamente adecuada?
Para pruebas, bastan 2 vCPU, 4 GB de RAM y 100 GB de SSD. Para un production pequeño con un backend, un mínimo razonable es 4 vCPU, 8 GB de RAM y 500 GB de NVMe. Si se ejecutan simultáneamente Garage, SeaweedFS, Caddy y las copias de seguridad, es mejor contar con un mínimo de 8 GB de RAM y, para carga activa, 16 GB. Deje libres entre el 20 y el 30 por ciento del disco para archivos temporales y recuperación.
¿Qué elegir para esta tarea: VPS o dedicated?
Un VPS es adecuado para development, bucket pequeños, copias de seguridad y cargas con I/O predecible. Es mejor elegir dedicated a partir de varios terabytes, con un número elevado de operaciones o cuando se necesita ECC, RAID/ZFS o rendimiento de disco garantizado. En ambos casos, una copia de seguridad externa es obligatoria. Un dedicated con un solo disco y sin copia no protege frente a fallos de hardware, errores del administrador o ransomware.
¿Se pueden ejecutar Garage y SeaweedFS en un mismo nodo en production?
Técnicamente es posible, pero genera competencia por RAM, CPU e I/O de disco. Este esquema es aceptable para migración, comparación o tareas pequeñas independientes. Para una operación permanente, elija un backend y desactive el segundo. Si se necesitan distintas clases de almacenamiento, es mejor separar los servicios por discos o servidores y establecer límites de recursos de Docker independientes.
¿Cómo hacer que el almacenamiento sea tolerante a fallos?
Un nodo único no es tolerante a fallos. Para Garage, utilice varios nodos, distintos failure domain y replication factor 3. Para SeaweedFS, distribuya master, volume y filer en varios servidores, habilite la replicación de volume y garantice almacenamiento separado para los metadatos. Los nodos deben tener alimentación independiente y, preferiblemente, no estar en el mismo failure domain físico. Incluso la replicación de clúster no sustituye una copia de seguridad offline o geográficamente remota.
¿Cómo migrar de un servicio a otro?
Cree un bucket en el nuevo endpoint y transfiera los objetos mediante un cliente S3, por ejemplo rclone sync o AWS CLI. Primero realice una migración de prueba de un bucket, compare el número de objetos, los tamaños y las sumas de comprobación. Después de cambiar la aplicación, mantenga el almacenamiento antiguo en modo de lectura durante el período de reversión. No copie directamente los directorios internos de Garage a SeaweedFS: la transferencia se realiza mediante la API de S3.
9. Conclusiones y próximos pasos
En un servidor dedicated se puede alojar un almacenamiento S3 propio con Garage o SeaweedFS, protegiendo la API mediante Caddy y HTTPS. Para un solo nodo se ha obtenido una configuración funcional con Docker Compose, directorios de datos independientes, firewall restringido y copias de seguridad mediante restic.
- Elija un backend principal y realice una prueba de carga con el tamaño real de los objetos.
- Añada un segundo servidor, replicación y monitorización si los datos son críticos para el negocio.
- Configure la ampliación automática de discos, la comprobación de restauración y las políticas lifecycle para objetos antiguos.