Instalar n8n con Docker Compose en un VPS

9 min de lectura·Matthieu|

Configura n8n 2.x con Docker Compose y PostgreSQL en un VPS. Listo para producción desde el inicio con versiones fijadas, respaldo de la clave de cifrado, health checks y límites de recursos.

Esta guía instala n8n 2.12.3 con Docker Compose y PostgreSQL en un VPS. Tendrás una instancia de n8n funcionando en unos 15 minutos. La configuración usa valores de producción desde el inicio: versiones de imagen fijadas, credenciales cifradas, puerto vinculado solo a localhost, health checks en ambos contenedores y límites de recursos Docker. No se cubre reverse proxy ni SSL aquí. Eso está en .

¿Qué necesito antes de instalar n8n?

Necesitas un VPS con Debian 12 o Ubuntu 24.04 y al menos 4 GB de RAM. Docker y Docker Compose (el plugin docker compose, no el antiguo binario docker-compose) deben estar instalados. Si aún no tienes Docker, sigue primero Docker Compose para despliegues multi-servicio en VPS.

Verifica que Docker Compose esté disponible:

docker compose version

Salida esperada:

Docker Compose version v2.x.x

Si este comando falla con docker: 'compose' is not a docker command, tienes el binario standalone antiguo. Instala el plugin Docker Compose desde el repositorio oficial de Docker.

También necesitas un usuario no-root con acceso sudo. Todos los comandos de esta guía se ejecutan con ese usuario, no como root.

¿Por qué usar PostgreSQL en lugar de SQLite para n8n?

PostgreSQL maneja conexiones concurrentes, soporta WAL para recuperación ante fallos y funciona con pg_dump para respaldos en caliente mientras n8n está corriendo. SQLite bloquea todo el archivo de base de datos en cada escritura. Con ejecuciones concurrentes de webhooks, esto provoca timeouts y corrupción de datos. No se puede respaldar una base de datos SQLite de forma segura mientras n8n está corriendo sin arriesgar una copia corrupta. Para cualquier uso más allá de pruebas locales, PostgreSQL es la opción correcta.

Característica SQLite PostgreSQL
Escrituras concurrentes Bloqueo de escritor único MVCC completo
Respaldos en caliente Inseguro en ejecución pg_dump en cualquier momento
Recuperación ante fallos Replay manual del journal Replay automático del WAL
Escalabilidad Proceso único Connection pooling
Variable de entorno n8n DB_TYPE=sqlite DB_TYPE=postgresdb

¿Cómo instalo n8n con Docker Compose en un VPS?

Crea un directorio de proyecto, escribe un archivo .env con tus secretos, escribe el docker-compose.yml, inicia el stack y verifica que todo esté saludable. Cada paso incluye una verificación.

Crear el directorio del proyecto

mkdir -p ~/n8n && cd ~/n8n

Crear el archivo .env

Todos los secretos van en un archivo .env. Nunca escribas contraseñas o claves directamente en docker-compose.yml.

Genera una contraseña de base de datos fuerte y la clave de cifrado de n8n:

echo "POSTGRES_PASSWORD=$(openssl rand -base64 32)" >> .env
echo "N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env

Ahora agrega las variables restantes:

cat >> .env << 'EOF'
# PostgreSQL
POSTGRES_USER=n8n
POSTGRES_DB=n8n

# n8n
N8N_VERSION=2.12.3
N8N_HOST=localhost
N8N_PORT=5678
N8N_PROTOCOL=http
N8N_DIAGNOSTICS_ENABLED=false
GENERIC_TIMEZONE=UTC
EOF

Restringe los permisos del archivo. Solo tu usuario debe poder leerlo:

chmod 600 .env

Verificación:

ls -la .env

Deberías ver -rw-------. Ningún otro usuario en el servidor puede leer tu contraseña de base de datos ni tu clave de cifrado.

¿Cómo genero y respaldo la clave de cifrado de n8n?

El N8N_ENCRYPTION_KEY se generó arriba con openssl rand -hex 32. Esto produce una clave aleatoria de 32 bytes (64 caracteres hexadecimales). n8n usa esta clave para cifrar todas las credenciales que almacenas: claves API, tokens OAuth, contraseñas de bases de datos en workflows. Si pierdes esta clave, todas las credenciales almacenadas quedan permanentemente ilegibles. No existe mecanismo de recuperación.

Respalda la clave de cifrado ahora. Cópiala a un gestor de contraseñas o una bóveda offline:

grep N8N_ENCRYPTION_KEY .env

Guarda el resultado en un lugar seguro fuera de este servidor. Hazlo antes de agregar tu primera credencial en n8n.

Referencia de variables de entorno

Variable Propósito Valor de ejemplo
POSTGRES_USER Nombre del superusuario PostgreSQL n8n
POSTGRES_PASSWORD Contraseña del superusuario PostgreSQL (generada, 32+ caracteres)
POSTGRES_DB Nombre de la base de datos n8n
N8N_VERSION Tag de imagen n8n fijado 2.12.3
N8N_ENCRYPTION_KEY Cifra las credenciales almacenadas (generada, 64 caracteres hex)
N8N_HOST Host para la interfaz de n8n localhost
N8N_PORT Puerto para la interfaz de n8n 5678
N8N_PROTOCOL HTTP o HTTPS http
N8N_DIAGNOSTICS_ENABLED Enviar telemetría a n8n false
GENERIC_TIMEZONE Zona horaria para triggers cron UTC

Escribir el docker-compose.yml

cat > docker-compose.yml << 'COMPOSE'
services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"
    security_opt:
      - no-new-privileges:true
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  n8n:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    restart: unless-stopped
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: ${N8N_PORT}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      N8N_DIAGNOSTICS_ENABLED: ${N8N_DIAGNOSTICS_ENABLED}
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
    ports:
      - "127.0.0.1:5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:5678/healthz || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: "2.0"
    security_opt:
      - no-new-privileges:true
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  postgres_data:
  n8n_data:
COMPOSE

Algunos puntos a notar en este archivo:

Sin clave version:. Docker Compose V2 la ignora. Todos los tutoriales de la competencia aún incluyen version: '3.7' o version: '3.8'. La especificación Compose depreció este campo. Incluirlo produce una advertencia en las versiones actuales de Docker.

Puerto vinculado a 127.0.0.1. La línea "127.0.0.1:5678:5678" restringe n8n a localhost. Esto es un requisito de seguridad, no una preferencia. Los mapeos de puertos de Docker evitan iptables y las reglas UFW por completo. Si escribes 5678:5678 sin el prefijo 127.0.0.1, n8n es accesible desde internet aunque tu firewall bloquee el puerto 5678. Un reverse proxy en la misma máquina reenviará el tráfico a localhost:5678 cuando lo configures.

security_opt: no-new-privileges:true evita que los procesos dentro del contenedor obtengan privilegios adicionales mediante binarios setuid o setgid. Es una medida de defensa en profundidad contra ataques de escape de contenedor.

Rotación de logs. El bloque logging limita el log JSON de cada contenedor a 3 archivos de 10 MB cada uno (30 MB máximo por servicio). Sin esto, los logs de Docker crecen hasta llenar tu disco. En un VPS con almacenamiento limitado, esto importa.

Health checks en ambos servicios. PostgreSQL usa pg_isready. n8n usa su endpoint /healthz. La condición depends_on asegura que n8n solo inicie después de que PostgreSQL pase su health check.

Límites de recursos. PostgreSQL obtiene 512 MB de RAM y 1 CPU. n8n obtiene 2 GB de RAM y 2 CPUs. Estos valores funcionan bien en un VPS de 4-8 GB. Ajústalos según el tamaño de tu servidor y la complejidad de tus workflows.

Volúmenes nombrados. Tanto postgres_data como n8n_data son volúmenes nombrados gestionados por Docker. Docker maneja la propiedad y los permisos dentro del volumen automáticamente. No necesitas crear directorios en el host ni corregir permisos manualmente.

restart: unless-stopped en lugar de restart: always. Ambos reinician tras un fallo, pero unless-stopped respeta los comandos manuales docker compose stop. Con restart: always, un contenedor detenido manualmente reinicia si el daemon Docker reinicia (por ejemplo, tras una actualización del sistema).

Iniciar el stack

cd ~/n8n
docker compose up -d

Observa los logs durante el primer inicio:

docker compose logs -f

Espera hasta ver que n8n muestre una línea con n8n ready on. Presiona Ctrl+C para salir del visor de logs.

Verificar la instalación

Comprueba que ambos contenedores estén corriendo y saludables:

docker compose ps

Salida esperada:

NAME       IMAGE                                  ...  STATUS                    PORTS
n8n-n8n-1       docker.n8n.io/n8nio/n8n:2.12.3   ...  Up X minutes (healthy)    127.0.0.1:5678->5678/tcp
n8n-postgres-1  postgres:16-alpine                ...  Up X minutes (healthy)

Fíjate bien: ambos contenedores muestran (healthy) en la columna STATUS. Esto confirma que los health checks están pasando. Si ves (health: starting), espera 30 segundos y verifica de nuevo.

Prueba la API de n8n desde el servidor:

curl -s http://localhost:5678/healthz

Salida esperada:

{"status":"ok"}

Verifica que el puerto solo escuche en localhost, no en todas las interfaces:

ss -tlnp | grep 5678

Deberías ver 127.0.0.1:5678 en la salida. Si ves 0.0.0.0:5678, tu binding de puerto es incorrecto. Detén el stack, corrige la línea ports en docker-compose.yml y reinicia.

¿Cómo creo la cuenta de propietario de n8n?

Abre un túnel SSH desde tu máquina local para acceder a n8n desde tu navegador:

ssh -L 5678:127.0.0.1:5678 your-user@your-server-ip

Ahora abre http://localhost:5678 en tu navegador. n8n muestra una pantalla de configuración en el primer acceso. Crea tu cuenta de propietario con una contraseña fuerte. Esta cuenta tiene acceso de administrador completo a n8n.

Después de crear la cuenta, cierra el túnel SSH. No dejes el puerto 5678 tunelizado más tiempo del necesario. Configura un reverse proxy con SSL para el acceso regular. Ver .

¿Cómo verifico que n8n está funcionando correctamente?

Después de crear tu cuenta y cerrar el túnel SSH, ejecuta una serie final de verificaciones desde el servidor.

Comprueba la salud de los contenedores:

docker compose ps --format "table {{.Name}}\t{{.Status}}"

Ambos servicios deben mostrar (healthy).

Revisa los logs de n8n en busca de errores:

docker compose logs n8n --tail 20

Busca líneas ERROR. Un inicio limpio muestra mensajes de migración de base de datos seguidos de n8n ready on.

Revisa los logs de PostgreSQL:

docker compose logs postgres --tail 10

Deberías ver database system is ready to accept connections.

Revisa el uso de disco de tus volúmenes Docker:

docker system df -v | grep -E "n8n|postgres"

Esto te dice cuánto espacio están usando n8n y PostgreSQL. Revísalo periódicamente en instancias VPS con almacenamiento limitado.

¿Algo salió mal?

El contenedor se detiene inmediatamente. Revisa los logs con docker compose logs n8n. Causas comunes: archivo .env faltante, contraseña de PostgreSQL incorrecta o la clave de cifrado contiene caracteres especiales que rompen la expansión de shell. Regenera tu .env si es necesario.

Errores de permisos. El contenedor de n8n corre como UID 1000. Si cambias de volúmenes nombrados a bind mounts, asegúrate de que el directorio del host pertenezca al UID 1000: sudo chown -R 1000:1000 ./n8n_data.

Health check fallando. n8n necesita 20-30 segundos para iniciar en el primer arranque mientras ejecuta las migraciones de base de datos. Si los health checks fallan, revisa docker compose logs n8n para errores de migración. La configuración start_period: 30s le da tiempo a n8n antes de que comiencen los health checks.

No puedo conectarme a n8n. El puerto está vinculado a localhost. No puedes acceder desde otra máquina sin un túnel SSH o reverse proxy. Esto es intencional.

Olvidé la clave de cifrado. Si perdiste N8N_ENCRYPTION_KEY y n8n tiene credenciales almacenadas, esas credenciales se perdieron. No hay recuperación posible. Por eso existe el paso de respaldo.

¿Qué debo hacer después de instalar n8n?

Esta instalación te da una instancia de n8n funcionando, accesible solo desde el servidor mismo. Para uso en producción, necesitas tres cosas más:

  1. Reverse proxy con SSL. Configura Nginx o Caddy frente a n8n con un certificado TLS. Esto te da acceso HTTPS con un nombre de dominio. Ver .

  2. Respaldos. Programa respaldos automatizados de la base de datos PostgreSQL y la clave de cifrado de n8n. Ver .

  3. Actualizaciones. Para actualizar n8n, cambia N8N_VERSION en .env a la nueva versión y luego ejecuta docker compose up -d. Docker descarga la nueva imagen y recrea el contenedor. Lee siempre las notas de versión de n8n antes de actualizar.

Para la guía principal sobre opciones de automatización de workflows en un VPS, ver .

Para los fundamentos de Docker Compose y gestión de múltiples servicios, ver Docker Compose para despliegues multi-servicio en VPS.


Copyright 2026 Virtua.Cloud. Todos los derechos reservados. Este contenido es una obra original del equipo de Virtua.Cloud. La reproducción, republicación o redistribución sin permiso escrito está prohibida.

¿Listo para probarlo?

Despliega tu propio servidor en segundos. Linux, Windows o FreeBSD.

Ver planes VPS