Mover un stack a otro servidor: no copies el contenedor, copia tres cosas
Me pidieron llevar un bot de n8n a otro VPS. La imagen es la oficial, asi que copiarla no sirve de nada. Lo que hay que llevar cabe en un renglon, y una de las tres cosas son 56 bytes que si se te olvidan, arranca todo y no funciona nada.
«¿Podemos copiar el contenedor y montarlo en otro servidor?»
La pregunta es razonable y la respuesta es que no, pero no porque sea difícil: porque el contenedor no es donde está lo que te importa.
Por qué copiar el contenedor no sirve
La imagen de ese servicio es n8nio/n8n:2.34.6, bajada de Docker Hub sin modificar. Un docker commit o un docker save me daría un archivo de 2.11 GB idéntico a lo que cualquiera puede bajar con un docker pull.
Y el estado —los flujos, las credenciales, las conversaciones— no está dentro del contenedor. Está en un volumen y en una base de datos. Por diseño: es lo que permite reconstruir el contenedor sin perder nada, que es la mitad del sentido de usar Docker.
Así que lo que se muda son tres cosas:
| Qué | Dónde vive | Peso |
|---|---|---|
| Flujos, credenciales, conversaciones | la base de datos | 92 MB |
| La clave de cifrado | /home/node/.n8n/config |
56 bytes |
| Configuración y secretos | compose.yml y .env |
2 KB |
Los 56 bytes que arruinan la migración
Esta es la parte que hay que saber de antemano, porque falla tarde y en silencio.
n8n cifra las credenciales guardadas —contraseñas de base de datos, llaves de API— con una clave que genera la primera vez que arranca y guarda en un archivo dentro de su volumen:
$ docker exec n8n-pycos cat /home/node/.n8n/config
{
"encryptionKey": "..."
}
Si migras la base de datos pero no llevas esa clave, el servidor nuevo arranca perfecto. Entras, ves tus flujos completos, ves las credenciales en la lista. Y al ejecutar, los nodos que usan una credencial fallan, porque la nueva instancia generó su propia clave y no puede descifrar lo que trajiste.
El síntoma no dice «clave de cifrado». Dice que la credencial no sirve. Es el clásico «migré n8n y no jala».
Lo limpio es no copiar el archivo, sino declarar la clave como variable de entorno en el destino:
N8N_ENCRYPTION_KEY=<la del servidor viejo>
Este patrón no es exclusivo de n8n. Laravel tiene su APP_KEY, y si la cambias las sesiones y todo lo cifrado con Crypt dejan de leerse. Grafana, Vaultwarden y media docena más tienen su equivalente. Cada vez que migres un servicio, la primera pregunta es cuál es su llave de cifrado y dónde vive.
El respaldo, con el servicio detenido
cd ~/apps/n8n-pycos
docker compose stop n8n
docker exec db-pycos pg_dump -U n8n -d pycos -Fc > /tmp/pycos.dump
docker compose start n8n
El stop de en medio no es paranoia. pg_dump toma una foto consistente, pero si el servicio sigue escribiendo mientras tanto, la foto es de un instante que ya pasó — y para un servicio con flujos corriendo cada minuto, eso significa ejecuciones a medias en el destino.
El formato -Fc es el comprimido de Postgres. Ocupa menos y pg_restore puede restaurar tablas sueltas si algo sale mal.
En el destino, dos ajustes antes de levantar
Quitar el directorio de inicialización. Si tu compose.yml monta un ./sql:/docker-entrypoint-initdb.d, ese script crea tablas en el primer arranque — y tu respaldo ya las trae. Chocan.
Cambiar el dominio. En n8n son N8N_HOST y WEBHOOK_URL; en Laravel, APP_URL y SESSION_DOMAIN. Si se te olvidan, el servicio funciona pero genera enlaces que apuntan al servidor viejo.
Y después de restaurar:
docker compose up -d db
docker exec -i db-pycos pg_restore -U n8n -d pycos --clean --if-exists < pycos.dump
docker compose up -d n8n
Lo último, que no está en ninguna guía
Desactivar y volver a activar los flujos desde la interfaz. n8n registra los webhooks en una tabla al activar un flujo, no al arrancar. Restauras la base, ves los flujos marcados como activos, y sus URLs devuelven 404 porque el registro apunta al host viejo.
Y apagar el origen. Si los dos servidores comparten un número de WhatsApp, una cuenta de API o una cola, dos instancias vivas se pelean los mensajes. La migración no termina cuando el destino funciona: termina cuando el origen está apagado.