Inglés

Ampliación BTCPay Server (Docker) a Nuevo NVMe

13 / 03 / 2026

Guía técnica personalizada para Raspberry Pi 5 con BTCPay Server y Docker.

Software:
• BTCPay Server (Docker)
• Nodos: Bitcoin On-chain + LND (LN)
• Gestión: Ride The Lightning (RTL)
• OS: Debian Trixie
Hardware:
• Raspberry Pi 5 (8GB RAM) + Miuzei
• HAT: Geekworm X1004 (Dual NVMe)
• UPS: Geekworm X1200 Shield
• Storage: 1TB (Boot) + 2TB (Data)
• Cooling: 2x Thermalright HR-09 2280
Cuidadín: No borrar los datos originales del disco de 1TB hasta que el nodo haya completado al menos 24h de funcionamiento perfecto en el nuevo disco.

1 Preparación del Hardware y UUID

Si el 2TB es un NVMe nuevo de fábrica, probablemente no tenga partición ni filesystem, por lo que lsblk -f no mostrará UUID (o mostrará "none"). En ese caso hay que particionar y formatear

sudo fdisk /dev/nvme1n1  # Crea una partición primaria (n, p, 1, default, w)
sudo mkfs.ext4 -L nvme_data /dev/nvme1n1p1  # Formatea como ext4 (rápido en NVMe)
sudo blkid /dev/nvme1n1p1  # Obtener el UUID 

Identificar el UUID de tu nuevo disco de 2TB (ej. nvme1n1p1):

sudo lsblk -f

En lugar de sudo blkid /dev/nvme1n1p1, se puede usar sudo blkid para listar todos, o lsblk -o NAME,UUID,LABEL,FSTYPE para una vista más clara.

NOTA: Usar blkid en lugar de solo lsblk -f para confirmar UUID, ya que es más preciso.
También, verificar con sudo smartctl -a /dev/nvme1n1 que el disco esté saludable (temperatura, errores) antes de proceder, especialmente con el HAT Geekworm X1004 (que a veces tiene quirks de detección en Pi5).

NOTA: Con el HAT dual NVMe, asegurarnos de que el Pi5 detecte ambos (nvme0n1 y nvme1n1).
Si se usa PCIe Gen3 (config en config.txt), podría mejorar performance, pero no es crítico.

Crear el punto de montaje y asignar permisos de root:

sudo mkdir -p /mnt/nvme_data
sudo chown root:root /mnt/nvme_data
sudo chmod 755 /mnt/nvme_data

2 Montaje Persistente (fstab)

Editar el archivo: sudo nano /etc/fstab

Añadir la línea al final (sustituyendo el UUID):

UUID=TU_NUEVO_UUID_AQUI  /mnt/nvme_data  ext4  defaults,noatime  0  2

Montar y verificar:

sudo mount -a
df -h | grep nvme_data

NOTA: Después de editar fstab, hacer sudo mount -a antes de reiniciar para probar.
Añadir sudo reboot al final de este paso y verifica post-reboot con df -h para confirmar persistencia.
Si se usa UPS (X1200), probar un corte de corriente simulado para ver si el montaje sobrevive. 

3 Parada Total de Servicios

Es crítico detener el socket de Docker para evitar reinicios automáticos:

# Backup de la wallet y canales
cp /mnt/nvme_data/docker/volumes/generated_lnd_bitcoin_datadir/_data/data/chain/bitcoin/mainnet/channel.backup /ruta/segura/

cd ~/btcpayserver-docker
./btcpay-down.sh

# Si sale "Network generated_default Resource is still in use" ...  no pasa ná.

sudo systemctl stop docker.service
sudo systemctl stop docker.socket
sudo systemctl mask docker.service   # opcional pero fuerte: bloquea starts hasta unmask
sudo systemctl mask docker.socket

# verificar:
sudo systemctl status docker.socket   # Debería decir inactive (dead)
sudo systemctl status docker.service  # inactive (dead)

NOTA: Si hay otros servicios dependiendo de Docker (ej. custom scripts o cron jobs), podrían interferir.
En Debian Trixie, systemd podría comportarse diferente; verificar con systemctl status docker post-stop.
Sugerencia: Agregar docker ps antes y después para confirmar que nada corre.
Si usa watchtower o auto-updates en BTCPay, los desactivamos temporalmente.

Estado de Docker: Añadir sudo systemctl disable docker justo después de pararlo si queremos una seguridad extra contra arranques automáticos durante el proceso, y no olvidar hacer sudo systemctl enable docker después del Paso 5.

Comprobar que ni Docker ni BTCPay están en ejecución:

# 1. Docker daemon parado? (debe fallar o decir inactive)
docker ps                          # Esperado: "Cannot connect to the Docker daemon" o error similar
systemctl status docker            # Debe decir: inactive (dead) o masked
systemctl status docker.socket     # inactive (dead) o masked

# 2. BTCPay containers? (deben estar ausentes o stopped)
docker ps -a | grep -i generated   # Nada o solo "Exited" si hay leftovers
docker ps | grep -i btcpay         # Nada si no corre nada

# 3. Extra: procesos dockerd (debe no aparecer)
ps aux | grep -i dockerd           # Solo grep o nada relevante

4 Migración con Progreso Real

Copiamos los datos, con visualización de progreso y preservación de atributos:

⏳ Sea paciente. En una RPi5 esto puede tardar entre 2 y 4 horas.

Flags de Rsync: La opción -n (dry-run) es útil para una primera pasada de prueba. Podríamos sugerir:

# Si se corta el ssh se corta el rsync
sudo rsync -avPHX --dry-run --progress /var/lib/docker/ /mnt/nvme_data/docker/ | wc -l

# Asñi podemos reanudar la copia
sudo nohup rsync -avPHX --progress --partial --append /var/lib/docker/ /mnt/nvme_data/docker/ > /home/$(whoami)/rsync.log 2>&1 &

#                       nohup : Evita que el proceso muera al cerrar SSH
#          --partial --append : Permite reanudar si se corta
# > /home/$(whoami)/rsync.log : Guarda toda la salida en un archivo de log
#                        2>&1 : Incluye errores en el mismo log
#                           & : Ejecuta en background

# Paso 2: Ver que arrancó
ps aux | grep rsync | grep -v grep

# Paso 3: Monitorear (opcional)
tail -f ~/rsync_migracion.log

# si el proceso se corta, se ejecuta exactamente el mismo comando y continúa desde donde estaba

# Se podría verificar la copia con
rsync -avPHX --progress --checksum /var/lib/docker/ /mnt/nvme_data/docker/
#  ... pero tardaría un huevo, o incluso un huevo y medio, así que
# mejor comparar estructuras y tamaños y listo:

sudo find /var/lib/docker -type f | wc -l
sudo find /mnt/nvme_data/docker -type f | wc -l
sudo du -sb /var/lib/docker/ | cut -f1
sudo du -sb /mnt/nvme_data/docker/ | cut -f1

# ... y los números deberían coincidor, si no, mal asunto.
Esto da una idea de la cantidad de archivos sin mover un solo byte.

Si el montaje falla o hay poco espacio temporal en RAM (Pi5 8GB es ok, pero con nodos grandes), rsync podría pausar.
No maneja exclusiones (ej. si hay logs temporales innecesarios).
También, si se interrumpe (power fail), rsync es resumible, pero con UPS se mitiga.
Sugerencia: Agregar --delete si quiere limpiar archivos obsoletos en destino (pero no en la primera pasada).
Monitorizar temp con vcgencmd measure_temp durante la copia (con cooling HR-09, debería estar por debajo de 70°C).
Verificar post-rsync con du -sh /var/lib/docker/ /mnt/nvme_data/docker/ para comparar tamaños.

5 Configuración de Docker (daemon.json)

Actualizar la configuración: sudo nano /etc/docker/daemon.json

Debe quedar exactamente así para mantener los ajustes de IPv6 y logs:

{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "journald",
  "iptables": true,
  "ipv6": false,
  "data-root": "/mnt/nvme_data/docker"
}
NOTA: Asegúrarse de que la coma después de "ipv6": false esté presente.

6 Verificación y Arranque

Primero, comprueba que el JSON no tiene errores sintácticos:

docker info (Si esto da error de conexión, es normal. Si da error de configuración, revisamos el paso 5)

Si todo es correcto, arrancamos:

sudo systemctl start docker
        
# y antes de lanzar btc-pay.up
# verificamos que Docker funciona y apunta al nuevo directorio

sudo systemctl status docker --no-pager -ldocker info --format '{{.DockerRootDir}}'

# ARRANCAMOS BTCPAY        

cd ~/btcpayserver-docker && ./btcpay-up.sh

#verifivcamos q los servicios están arriba:

docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(bitcoin|lnd|rtl|btcpayserver)"

#Y los volumenes bien montados:

docker volume ls

docker info | grep "Docker Root Dir"
# Debería devolver: /mnt/nvme_data/docker

# Verificar que no hay errores de base de datos corrupta
docker logs generated_lnd_bitcoin_1 --tail 100

Sincronización del nodo: El indicador final. En RTL o en los logs de bitcoind (docker logs generated_bitcoin_1 --tail 50), verificamos que la altura del bloque esté aumentando y que no haya errores de "Headers are out of sync" o similares.

Verificación final: Una vez arrancado, entramos al panel de BTCPay o RTL y verificamos que el "Disk Space" refleje los 2TB disponibles.