n8n Installation sécurisée avec Traefik & PostgreSQL sur Docker

Ce tutoriel vous guidera pour déployer une instance n8n robuste, sécurisée et prête pour la production sur un serveur Ubuntu 24.x LTS. Nous utiliserons la pile technologique suivante :

Vous n’avez pas encore votre instance ? Créez un compte n8n puis déployez-la sur un VPS Contabo (excellent rapport qualité/prix pour l’auto-hébergement).

  • n8n : La plateforme d’automatisation des workflows.
  • PostgreSQL : Une base de données puissante pour vos données.
  • Traefik : Un reverse proxy moderne pour la gestion HTTPS.
  • Docker & Docker Compose : Pour conteneuriser nos services.

1. Prérequis

Avant de commencer, assurez-vous de disposer des éléments suivants :

✅ Un serveur Linux (ce guide est spécifiquement adapté pour Ubuntu 24.x LTS).

✅ Un nom de domaine pointant vers l’adresse IP de votre serveur via un enregistrement DNS de type A.

✅ Les ports 80 (HTTP) et 443 (HTTPS) ouverts sur le pare-feu de votre serveur.

2. Configuration Initiale du Serveur (Ubuntu 24)

Ces étapes préparent votre serveur pour un environnement sécurisé et fonctionnel.

a. Création d’un utilisateur non-root (Sécurité)

Il est fortement déconseillé d’opérer en tant que root.

# Connectez-vous en root à votre serveur
# Remplacez 'votre_utilisateur' par le nom d'utilisateur de votre choix
adduser votre_utilisateur

# Donnez à cet utilisateur les privilèges 'sudo'
usermod -aG sudo votre_utilisateur

# Déconnectez-vous et reconnectez-vous avec votre nouvel utilisateur
su - votre_utilisateur

Toutes les commandes suivantes seront exécutées avec cet utilisateur.

b. Changement du nom d’hôte

# Remplacez 'votre_serveur' par le nom d'hôte de votre choix

# 1. Définir le nom d'hôte (immédiat et dans /etc/hostname)
sudo hostnamectl set-hostname votre_serveur

# 2. Mettre à jour la résolution locale
sudo nano /etc/hosts

# Modifiez/Assurez-vous que la ligne 127.0.1.1 pointe vers le nouveau nom :
# 127.0.0.1       localhost
# 127.0.1.1       votre_serveur

# 3. Assurer la persistance sur Contabo (cloud-init)
sudo nano /etc/cloud/cloud.cfg
hostname: votre_serveur
fqdn: votre_serveur
preserve_hostname: true (verif, seule cette ligne semble nécessaire)
manage_etc_hosts: false # Dire à cloud-init de ne JAMAIS toucher au fichier /etc/hosts.


# 4. Redémarrer pour valider la persistance
sudo reboot

Pour que le changement soit visible dans votre terminal, déconnectez-vous et reconnectez-vous.

c. Mises à jour du système et sécurité

# Mettez à jour les paquets manuellement une première fois
sudo apt update && sudo apt upgrade -y

# Installez le paquet pour les mises à jour automatiques
sudo apt install unattended-upgrades -y

# Activez et configurez les mises à jour automatiques (choisissez "Oui")
sudo dpkg-reconfigure --priority=low unattended-upgrades

d. Configuration du pare-feu (UFW)

sudo ufw allow 22/tcp   # Port SSH
sudo ufw allow 80/tcp   # Port HTTP pour Traefik
sudo ufw allow 443/tcp  # Port HTTPS pour Traefik
sudo ufw enable         # Activez le pare-feu
sleep 2
sudo ufw status         # Vérifiez la configuration

e. Protection contre les attaques Brute Force (Fail2Ban)

sudo apt install fail2ban -y
sudo systemctl restart fail2ban
sleep 2
sudo fail2ban-client status

DNS Configuration

Par défaut, les serveurs VPS utilisent les DNS de Contabo, ce qui peut provoquer des erreurs getaddrinfo EAI_AGAIN. Pour corriger cela, passez aux DNS de Google :

sudo nano /etc/systemd/resolved.conf

Modifiez la section [Resolve] :

[Resolve]
DNS=8.8.8.8 8.8.4.4
FallbackDNS=1.1.1.1 9.9.9.9

Forcez /etc/resolv.conf à pointer vers les DNS de Google et redémarrez le service :

sudo mv /etc/resolv.conf /etc/resolv.conf.old
echo -e "nameserver 8.8.8.8\nnameserver 8.8.4.4" | sudo tee /etc/resolv.conf
sudo chattr +i /etc/resolv.conf
systemctl restart systemd-resolved

Vérifiez la configuration DNS :

resolvectl status

Installation de Docker et Docker Compose

# Créez un répertoire temporaire pour le script d'installation
mkdir ~/tmp
cd ~/tmp

# setup-docker-repository.sh
# Configures the official Docker APT repository on an Ubuntu system.

# Update the apt package index and install prerequisite packages.
sudo apt-get update
sudo apt-get install -y ca-certificates curl

# Add Docker's official GPG key.
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Set up the repository.
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Update the apt package index again with the new repository.
sudo apt-get update

sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Ajoutez votre utilisateur au groupe Docker pour lancer des commandes sans 'sudo'
sudo usermod -aG docker $USER

# --- IMPORTANT ---
# Vous devez vous déconnecter puis vous reconnecter pour que ce changement soit pris en compte.
# Après reconnexion, vérifiez que l'installation a réussi :
docker run hello-world

3. Création du Réseau Partagé

Pour que Traefik puisse communiquer avec n8n, ils doivent être sur le même réseau Docker.

docker network create traefik_proxy

4. Déploiement de Traefik 🚦

a. Préparation de l’environnement Traefik

# Créez et accédez au répertoire pour Traefik
mkdir ~/traefik
cd ~/traefik

touch docker-compose.yml

# Créez le fichier qui stockera les certificats SSL
touch acme.json
chmod 600 acme.json

# Créez le fichier .env pour Traefik
nano .env

Dans le fichier .env, collez la ligne suivante en remplaçant par votre email :

# Votre email pour la génération des certificats SSL Let's Encrypt
[email protected]

b. Création du fichier docker-compose.yml pour Traefik

Créez le fichier ~/traefik/docker-compose.yml et collez-y le contenu suivant.

nano docker-compose.yml
services:
  traefik:
    image: traefik:v3
    container_name: traefik
    restart: always
    ports:
      - "80:80"
      - "443:443"
    command:
      - "--api=false"
      - "--api.insecure=false"
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=traefik_proxy
      - --certificatesresolvers.letsencrypt.acme.httpchallenge=true
      - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
      - --certificatesresolvers.letsencrypt.acme.email=${SSL_EMAIL}
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./acme.json:/letsencrypt/acme.json
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - traefik_proxy
    environment:
      - TZ=Europe/Paris 

networks:
  traefik_proxy:
    external: true

c. Lancement de Traefik

# Depuis le répertoire ~/traefik
docker compose up -d

5. Déploiement de n8n avec PostgreSQL 🚀

a. Préparation de l’environnement n8n

# Créez le répertoire de configuration pour n8n
mkdir ~/n8n
cd ~/n8n


# --- PRÉPARATION DU RÉPERTOIRE PARTAGÉ ET DES PERMISSIONS ---

# 1. Créer un groupe commun qui servira de pont entre n8n et l'utilisateur SFTP
sudo groupadd donnees_partagees

# 2. Créer le répertoire qui sera partagé
sudo mkdir -p /local-files/ftp

# 3. Appliquer les permissions au répertoire partagé
#    Le propriétaire est l'utilisateur du conteneur (1000).
#    Le groupe est notre groupe partagé.
sudo chown 1000:donnees_partagees /local-files/ftp

# Le mode "2775" est crucial :
# - '2' (setgid): force les nouveaux fichiers/dossiers créés à l'intérieur
#   à hériter du groupe 'donnees_partagees'.
# - '775': donne les pleins droits au propriétaire (n8n) et au groupe (utilisateur SFTP),
#   et un accès en lecture seule aux autres.
sudo chmod 2775 /local-files/ftp
sudo find /local-files/ftp -type d -exec chmod 2775 {} \;
sudo find /local-files/ftp -type f -exec chmod 664 {} \;


# Créez le fichier .env pour n8n
nano .env

Dans ce fichier .env, collez le contenu suivant et adaptez-le. Pour générer votre clé de chiffrement, exécutez openssl rand -base64 32.

# --- CONFIGURATION GÉNÉRALE ---
GENERIC_TIMEZONE=Europe/Paris

# --- DOMAINE N8N ---
N8N_HOSTNAME=n8n.exemple.com

# --- CLÉ DE CHIFFREMENT (TRÈS IMPORTANT) --- openssl rand -base64 32
N8N_ENCRYPTION_KEY=VOTRE_CLE_DE_CHIFFREMENT_A_CHANGER

# --- TASK RUNNERS ---
N8N_RUNNERS_ENABLED=true

# --- IDENTIFIANTS POSTGRESQL ---
POSTGRES_DB=n8n
POSTGRES_USER=n8n_user
POSTGRES_PASSWORD=VOTRE_MOT_DE_PASSE_TRES_SOLIDE_A_CHANGER

# --- SÉCURITÉ ET PERMISSIONS ---
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
UID=1000
GID=1001

Trouvez l’ID de notre nouveau groupe avec la commande :

getent group donnees_partagees | cut -d: -f3

Pour trouver votre UID et GID, exécutez id -u et id -g.

b. Création du fichier docker-compose.yml pour n8n

Créez le fichier ~/n8n/docker-compose.yml et collez-y cette configuration.

nano ~/n8n/docker-compose.yml
services:
  db:
    image: postgres:15-alpine
    container_name: n8n_postgres
    restart: always
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - traefik_proxy
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10

  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: always
    user: "${UID}:${GID}"
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=db
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - DB_POSTGRESDB_SSL=false
      - N8N_HOST=${N8N_HOSTNAME}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - EXECUTIONS_DATA_PRUNE=true
      - EXECUTIONS_DATA_MAX_AGE=720
      - WEBHOOK_URL=https://${N8N_HOSTNAME}/
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      - N8N_RUNNERS_ENABLED=${N8N_RUNNERS_ENABLED}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=${N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS}
      - NODE_FUNCTION_ALLOW_EXTERNAL=*
      - NODE_FUNCTION_ALLOW_BUILTIN=*
      - NODE_PATH=/home/node/.n8n/node_modules
      - N8N_PROXY_HOPS=1
    volumes:
      - n8n_data:/home/node/.n8n
      - /local-files:/files
    networks:
      - traefik_proxy
    depends_on:
      db:
        condition: service_healthy
    labels:
      - traefik.enable=true
      - traefik.http.routers.n8n.rule=Host(`${N8N_HOSTNAME}`)
      - traefik.http.routers.n8n.entrypoints=websecure
      - traefik.http.routers.n8n.tls.certresolver=letsencrypt
      - traefik.http.services.n8n.loadbalancer.server.port=5678
      - traefik.http.routers.n8n.middlewares=n8n-secure-headers@docker
      - traefik.http.middlewares.n8n-secure-headers.headers.forceSTSHeader=true
      - traefik.http.middlewares.n8n-secure-headers.headers.stsSeconds=31536000
      - traefik.http.middlewares.n8n-secure-headers.headers.stsIncludeSubdomains=true
      - traefik.http.middlewares.n8n-secure-headers.headers.stsPreload=true
      - traefik.http.middlewares.n8n-secure-headers.headers.contentTypeNosniff=true
      - traefik.http.middlewares.n8n-secure-headers.headers.browserXssFilter=true

networks:
  traefik_proxy:
    external: true

volumes:
  n8n_data:
  postgres_data:

c. Lancement de n8n et PostgreSQL

# Depuis le répertoire ~/n8n
docker compose up -d

Options de confort

Couleurs ls / répertoires + Backup nano

dircolors -p | sed -e 's/^DIR 01;34/DIR 01;36/' -e 's/^OTHER_WRITABLE 34;42/OTHER_WRITABLE 01;36/' > ~/.dircolors && eval "$(dircolors -b ~/.dircolors)"
mkdir -p ~/.nano_backups; touch ~/.nanorc; grep -qxF 'set backup' ~/.nanorc || echo 'set backup' >> ~/.nanorc; grep -qxF 'set backupdir ~/.nano_backups' ~/.nanorc || echo 'set backupdir ~/.nano_backups' >> ~/.nanorc

6. Configuration d’un accès SFTP sécurisé (Optionnel)

Cette section explique comment créer un accès SFTP sécurisé pour un utilisateur. Pour une sécurité maximale, l’utilisateur sera « emprisonné » (chroot) dans son propre répertoire personnel (/home/utilisateur_sftp). Un « pont » technique (mount --bind) sera ensuite créé pour lui donner accès au répertoire partagé /local-files/ftp depuis sa prison.

a. Création de l’utilisateur et ajout au groupe partagé

# 1. Créez un utilisateur standard (son /home sera sa prison)
sudo adduser utilisateur_sftp

# 2. Ajoutez cet utilisateur au groupe partagé pour lui donner les droits d'écriture
sudo usermod -aG donnees_partagees utilisateur_sftp

b. Préparation de la prison « Chroot »

La prison de l’utilisateur sera son répertoire /home. Pour des raisons de sécurité, il doit appartenir à root.

# Le répertoire /home/utilisateur_sftp devient la prison et doit appartenir à root
sudo chown root:root /home/utilisateur_sftp
sudo chmod 755 /home/utilisateur_sftp

c. Configuration du service SSH

Modifiez le fichier de configuration SSH pour appliquer la prison.

  1. Ouvrez le fichier : sudo nano /etc/ssh/sshd_config
  2. Ajoutez ce bloc à la toute fin du fichier :
# --- Configuration de la prison SFTP pour l'utilisateur ---
Match User utilisateur_sftp
    ForceCommand internal-sftp
    PasswordAuthentication yes
    ChrootDirectory /home/utilisateur_sftp
    PermitTunnel no
    AllowAgentForwarding no
    AllowTcpForwarding no
    X11Forwarding no

Testez et appliquez la configuration :

sudo sshd -t
sudo systemctl restart ssh

d. Création du « pont » vers le répertoire partagé

Cette étape projette le répertoire partagé à l’intérieur de la prison de l’utilisateur.

  1. Créez un point de montage dans la prison :
sudo mkdir /home/utilisateur_sftp/partage_ftp

Rendez le montage permanent via /etc/fstab (pour qu’il survive à un redémarrage).

Attention : une erreur dans ce fichier peut empêcher le serveur de démarrer.

# 1. Sauvegardez le fichier fstab par sécurité
sudo cp /etc/fstab /etc/fstab.bak

# 2. Ouvrez fstab pour le modifier
sudo nano /etc/fstab

# 3. Ajoutez cette ligne à la toute fin du fichier
/local-files/ftp   /home/utilisateur_sftp/partage_ftp   none   bind   0   0

Appliquez le montage sans redémarrer :

Si la commande ne retourne aucune erreur, la configuration est réussie.

sudo mount -a

e. Connexion avec un client SFTP (ex: FileZilla)

  • Hôte : L’adresse IP de votre serveur
  • Protocole : SFTP - SSH File Transfer Protocol
  • Port : 22 (ou votre port SSH si vous l’avez changé)
  • Identifiant : utilisateur_sftp
  • Mot de passe : Celui que vous avez défini.

En cas d’ajout de nouveaux répertoires dans /local-files/ftp

sudo mkdir /local-files/ftp/nouveau_repertoire && sudo chmod 2775 /local-files/ftp/nouveau_repertoire

7. Sauvegarde et Maintenance

Pour une installation de production, une stratégie de sauvegarde et de maintenance est cruciale.

a. Stratégie de Sauvegarde

Pensez à sauvegarder régulièrement les éléments suivants :

  • Base de données PostgreSQL : Contient tous vos workflows et exécutions.
  • Configuration et données n8n : Inclut les identifiants chiffrés.
  • Certificats Traefik : Pour éviter d’atteindre les limites de Let’s Encrypt lors d’une restauration.

⚠️ Si /files/ftp/backup/backup.lock (/local-files/ftp/backup/backup.lock depuis le container) est présent la sauvegarde est interrompue.

Exemple de script de sauvegarde :

mkdir ~/backup
cd ~/backup
nano backup_n8n.sh
chmod 700 backup_n8n.sh
#!/bin/bash
# ==============================================================================
# ---                           INITIALISATION                               ---
# ==============================================================================

# Trouve le répertoire où se trouve le script lui-même
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
ENV_FILE="${SCRIPT_DIR}/.env"

# Vérifie la présence du fichier .env à côté du script
if [ ! -f "$ENV_FILE" ]; then
    echo "ERREUR: Fichier .env introuvable dans ${SCRIPT_DIR}" >&2
    exit 1
fi

# Charge toutes les variables depuis le fichier .env
set -a
source "$ENV_FILE"
set +a

# Vérifie que la variable la plus importante a bien été chargée
if [ -z "$N8N_PROJECT_PATH" ]; then
    echo "ERREUR: La variable N8N_PROJECT_PATH n'est pas définie dans le fichier .env." >&2
    exit 1
fi

# ==============================================================================
# ---                           FONCTIONS UTILES                               ---
# ==============================================================================

# Fonction pour envoyer des notifications à Discord
send_discord_notification() {
    local status="$1"
    local title="$2"
    local message="$3"

    if [ -z "${DISCORD_WEBHOOK_URL}" ]; then return; fi

    if [ "$status" == "success" ]; then
        local color=3066993 # Vert
        local ping_content=""
    else # "failure" ou autre
        local color=15158332 # Rouge
        local ping_content="@everyone"
    fi

    local json_payload
    json_payload=$(cat <<EOF
{
  "content": "$ping_content",
  "username": "Sauvegarde ${SERVER_HOSTNAME}",
  "avatar_url": "https://i.imgur.com/4M34hi2.png",
  "embeds": [{
    "title": "$title",
    "description": "$message",
    "color": $color
  }],
  "allowed_mentions": {
    "parse": ["everyone"]
  }
}
EOF
)
    curl -s -H "Content-Type: application/json" -X POST -d "$json_payload" "$DISCORD_WEBHOOK_URL" > /dev/null
}

# ==============================================================================
# ---                           DÉBUT DU SCRIPT                                ---
# ==============================================================================

# Se placer dans le répertoire du projet n8n. C'est l'étape la plus importante.
cd "${N8N_PROJECT_PATH}" || { echo "ERREUR: Le dossier du projet n8n '${N8N_PROJECT_PATH}' est introuvable." >&2; exit 1; }

# Définir le nom du projet à partir du nom du dossier
PROJECT_NAME=$(basename "$PWD")

# Récupérer le nom de l'hôte pour l'utiliser dans les notifications
SERVER_HOSTNAME=$(hostname)

# Créer les dossiers nécessaires s'ils n'existent pas
mkdir -p "$BACKUP_STORAGE_PATH"
mkdir -p "${LOCAL_FILES_PATH}/ftp/backup"

# 1. VÉRIFIER L'ABSENCE DE TRAITEMENT EN COURS (VIA FICHIER LOCK)
LOCK_FILE_FULL_PATH="${LOCAL_FILES_PATH}/ftp/backup/${LOCK_FILE_NAME}"

echo "Vérification de l'absence de traitement en cours..."
if [ -f "$LOCK_FILE_FULL_PATH" ]; then
    MSG="Sauvegarde impossible, un traitement est en cours (fichier de verrouillage détecté)."
    echo "ERREUR: $MSG"
    send_discord_notification "failure" "ℹ️ Sauvegarde Annulée" "$MSG"
    exit 1
else
    echo "Aucun traitement en cours détecté."
fi

# VÉRIFIER LA PRÉSENCE DES FICHIERS DE CONFIGURATION DANS LE DOSSIER PROJET
if [ ! -f "docker-compose.yml" ] || [ ! -f ".env" ]; then
    MSG="Sauvegarde impossible : 'docker-compose.yml' ou '.env' est manquant dans ${N8N_PROJECT_PATH}."
    echo "ERREUR: $MSG"
    send_discord_notification "failure" "❌ Échec Critique de la Sauvegarde" "$MSG"
    exit 1
fi
echo "Fichiers de configuration trouvés."

# 2. Arrêter les services (docker compose est exécuté depuis le bon dossier)
echo "Arrêt des conteneurs..."
docker compose stop
if [ $? -ne 0 ]; then
    send_discord_notification "failure" "❌ Échec Critique de la Sauvegarde" "Impossible d'arrêter les conteneurs."
    exit 1
fi

# 3. Créer l'archive
SNAPSHOT_NAME="${PROJECT_NAME}_$(date +%Y-%m-%dT%H%M).tar.gz"
echo "Création de la sauvegarde : ${SNAPSHOT_NAME}"
FULL_N8N_VOLUME="${PROJECT_NAME}_${N8N_VOLUME_NAME}"
FULL_POSTGRES_VOLUME="${PROJECT_NAME}_${POSTGRES_VOLUME_NAME}"

if ! docker volume inspect "$FULL_N8N_VOLUME" > /dev/null 2>&1 || ! docker volume inspect "$FULL_POSTGRES_VOLUME" > /dev/null 2>&1; then
    send_discord_notification "failure" "❌ Échec Critique de la Sauvegarde" "Un ou plusieurs volumes Docker sont introuvables."
    docker compose up -d
    exit 1
fi

docker run --rm \
    -v "${FULL_N8N_VOLUME}:/backup-source/n8n_data:ro" \
    -v "${FULL_POSTGRES_VOLUME}:/backup-source/postgres_data:ro" \
    -v "${LOCAL_FILES_PATH}:/backup-source/local-files:ro" \
    -v "$(pwd)/docker-compose.yml":/backup-source/docker-compose.yml:ro \
    -v "$(pwd)/.env":/backup-source/.env:ro \
    -v "${BACKUP_STORAGE_PATH}":/backup-dest \
    busybox tar -czf "/backup-dest/${SNAPSHOT_NAME}" --exclude='tmp' --exclude='*.gz' -C /backup-source .

if [ $? -ne 0 ]; then
    send_discord_notification "failure" "❌ Échec Critique de la Sauvegarde" "La création de l'archive a échoué."
    docker compose up -d
    exit 1
fi
echo "Sauvegarde créée avec succès."

# 4. Redémarrer les services
echo "Correction des permissions sur /local-files/ftp..."
if [ -d "${LOCAL_FILES_PATH}/ftp" ]; then
    find "${LOCAL_FILES_PATH}/ftp" -type d -exec chmod 2775 {} \;
    find "${LOCAL_FILES_PATH}/ftp" -type f -exec chmod 664 {} \;
fi
echo "Redémarrage des conteneurs..."
docker compose up -d

# 5. Gérer la rétention
echo "Nettoyage des anciennes sauvegardes..."
find "${BACKUP_STORAGE_PATH}" -name "${PROJECT_NAME}_*.tar.gz" -type f -mtime +"$((RETENTION_DAYS - 1))" -delete

# 6. Envoyer la notification de succès
send_discord_notification "success" "✅ Sauvegarde Réussie" "La sauvegarde a été effectuée avec succès et inclut les fichiers de configuration.\\n**Fichier :** \`${SNAPSHOT_NAME}\`"

echo "Processus de sauvegarde terminé avec succès."

Paramètres .env (pour les notifs)

# --- CHEMINS IMPORTANTS ---
N8N_PROJECT_PATH="/home/phil/n8n"
# Indiquez le chemin absolu où les archives de sauvegarde seront stockées
BACKUP_STORAGE_PATH="/opt/n8n_backups"

# --- Configuration du Fichier de Verrouillage ---
LOCK_FILE_NAME="backup.lock"
LOCAL_FILES_PATH="/local-files"

# --- Configuration générale de la sauvegarde ---
N8N_VOLUME_NAME="n8n_data"
POSTGRES_VOLUME_NAME="postgres_data"
RETENTION_DAYS=30

# --- Notifications Discord ---
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/xxx"

Vous pouvez automatiser ce script avec une tâche cron.

(crontab -l 2>/dev/null; echo "0 3 * * * /home/phil/backup/backup_n8n.sh") | crontab -

b. Restauration

Cette procédure explique comment restaurer une sauvegarde sur un nouveau serveur, en écrasant une éventuelle installation existante.

Phase 1 : Préparation

  1. Transférez votre archive de sauvegarde (.tar.gz) sur le serveur de destination, par exemple dans /opt/n8n_backups/ en utilisant scp.

Phase 2 : Destruction de l’ancienne instance (Optionnel) Cette phase n’est nécessaire que si vous remplacez une instance existante.

  1. Placez-vous dans le dossier du projet n8n à effacer : cd /chemin/vers/ancien/n8n
  2. Arrêtez et supprimez complètement l’environnement (conteneurs ET volumes de données).

⚠️ Attention : Cette action est irréversible.

docker compose down -v

Phase 3 : Restauration

  1. Créez un dossier propre pour votre nouvelle instance et placez-vous à l’intérieur.
    mkdir -p /root/n8n cd /root/n8n
  2. Extrayez l’intégralité de l’archive dans ce nouveau dossier.Bash# Adaptez le chemin et le nom de l'archive tar zxf /opt/n8n_backups/votre_archive.tar.gz
  3. Maintenant que docker-compose.yml est présent, créez les volumes Docker vides.
    docker compose up -d docker compose stop
  4. Restaurez les données de l’archive dans les volumes Docker fraîchement créés.
    # Adaptez les noms des volumes et de l'archive
    docker run --rm \ -v n8n_n8n_data:/restore-dest/n8n_data \ -v n8n_postgres_data:/restore-dest/postgres_data \ -v "$(pwd)/local-files":/restore-dest/local-files \ -v /opt/n8n_backups:/backup-source:ro \ busybox tar xzf "/backup-source/votre_archive.tar.gz" -C /restore-dest

Phase 4 : Finalisation

  1. Corrigez les permissions des fichiers restaurés. C’est une étape cruciale pour éviter les erreurs.
    docker run --rm -v n8n_n8n_data:/data busybox chown -R 1000:1000 /data
  2. Démarrez votre instance n8n restaurée !Bashdocker compose up -d
  3. Vérifiez les logs (docker compose logs -f n8n) et accédez à l’interface via votre navigateur pour confirmer que tout est en ordre.

c. test de restauration

Cette procédure utilise un script automatisé pour tester une sauvegarde n8n de manière rapide, fiable et totalement isolée de votre environnement de production.

Le script gère la création d’un environnement de test (dans un dossier séparé, avec un port et des volumes distincts), sa vérification, et son nettoyage complet et interactif. Il ne modifiera jamais votre instance de production.

Étape 1 : Créer le script de test

Créez le fichier suivant, par exemple dans votre dossier ~/backup/ :

nano test_restore.sh

chmod +x test_restore.sh

Collez-y le contenu suivant :

#!/bin/bash
#
# test_restore.sh
# Purpose: Reliable and simple test restore for n8n in an isolated environment.
#
# Usage: ./test_restore.sh /path/to/your/backup.tar.gz
#
# This script performs a restore in an isolated environment to never
# impact the production instance. It automatically cleans up
# previous tests and provides a fully automated verify/cleanup cycle.
#
set -e # Stop the script immediately if a command fails

# ==============================================================================
# ---                       CONFIGURATION                                    ---
# ==============================================================================
# Directory where the test restore will be performed (resolves '~')
TEST_DIR="~/n8n_restored_instance"

# Docker project name to isolate test containers and volumes
TEST_PROJECT_NAME="n8nrestored"

# Original n8n port in the docker-compose.yml
PROD_PORT="5678"

# New port for the test instance (ensure it is free)
TEST_PORT="5679"

# Names of the volumes as defined in the compose file (relative, not full name)
N8N_VOLUME_NAME="n8n_data"
POSTGRES_VOLUME_NAME="postgres_data"

# Verification settings
VERIFY_TIMEOUT_SECONDS=120 # 2 minutes
VERIFY_POLL_INTERVAL=5     # 5 seconds
# ==============================================================================


# --- ARGUMENT VALIDATION ---
if [ -z "$1" ]; then
    echo "ERROR: You must provide the path to the backup file as an argument." >&2
    echo "Usage: $0 /path/to/your/backup.tar.gz" >&2
    exit 1
fi

BACKUP_FILE_PATH=$(realpath "$1") # Get absolute path

if [ ! -f "$BACKUP_FILE_PATH" ]; then
    echo "ERROR: Backup file '${BACKUP_FILE_PATH}' not found." >&2
    exit 1
fi

# --- SCRIPT START ---
echo "### STARTING N8N TEST RESTORE ###"

# Resolve the test directory path (handles '~')
EVAL_TEST_DIR=$(eval echo "$TEST_DIR")
BACKUP_FILENAME=$(basename "$BACKUP_FILE_PATH")
SERVER_IP=$(hostname -I | awk '{print $1}')
TEST_URL="http://${SERVER_IP}:${TEST_PORT}"

# 0. Preventive cleanup of any previous test environment
echo "[0/6] Cleaning up any previous test environment..."
if [ -f "${EVAL_TEST_DIR}/docker-compose.yml" ]; then
    cd "$EVAL_TEST_DIR"
    echo "      Shutting down old test containers..."
    docker compose -p "$TEST_PROJECT_NAME" down -v --remove-orphans > /dev/null 2>&1 || true
    cd ~ # Go back home for deletion
fi
echo "      Deleting old test directory..."
rm -rf "$EVAL_TEST_DIR"
echo "      Test environment is clean."

# 1. Prepare new test environment
echo "[1/6] Preparing new environment in ${EVAL_TEST_DIR}..."
mkdir -p "$EVAL_TEST_DIR"
cd "$EVAL_TEST_DIR"
echo "      Extracting archive (this may take a moment)..."
tar -xzf "$BACKUP_FILE_PATH"

# 2. Adapt configuration files
echo "[2/6] Adapting configuration for isolation..."
echo "      Neutralizing fixed container names in docker-compose.yml..."
# Comment out any 'container_name' directive to prevent conflicts
sed -i -E 's/^\s*(container_name:.*)/#\1/g' docker-compose.yml

echo "      Generating port and environment override file..."
# This override file only changes the port and adds the secure cookie setting.
cat << EOF > docker-compose.test-override.yml
# This file is auto-generated by test_restore.sh
# It isolates the test environment, overriding ports and env vars.
services:
  n8n: # Assuming 'n8n' is the service name
    ports:
      - "${TEST_PORT}:${PROD_PORT}" # Map to new test port
    environment:
      - N8N_SECURE_COOKIE=false # Disable secure cookie for easy HTTP testing
EOF
echo "      Configuration docker-compose.test-override.yml created."

# 3. Create and populate new test volumes
echo "[3/6] Creating and populating test Docker volumes..."
FULL_N8N_VOLUME="${TEST_PROJECT_NAME}_${N8N_VOLUME_NAME}"
FULL_POSTGRES_VOLUME="${TEST_PROJECT_NAME}_${POSTGRES_VOLUME_NAME}"

docker volume create "$FULL_N8N_VOLUME" > /dev/null
docker volume create "$FULL_POSTGRES_VOLUME" > /dev/null

# Copy data from extracted folders into the new named volumes
docker run --rm -v "$(pwd)/${N8N_VOLUME_NAME}:/src:ro" -v "$FULL_N8N_VOLUME:/dest" busybox cp -a /src/. /dest/
docker run --rm -v "$(pwd)/${POSTGRES_VOLUME_NAME}:/src:ro" -v "$FULL_POSTGRES_VOLUME:/dest" busybox cp -a /src/. /dest/
echo "      Test volumes are ready."

# 4. Start the restored instance
echo "[4/6] Starting test containers (Project: ${TEST_PROJECT_NAME})..."
# Use both files: the modified original and the override
docker compose -p "$TEST_PROJECT_NAME" -f docker-compose.yml -f docker-compose.test-override.yml up -d

# 5. Automated verification
echo "[5/6] Verifying test instance at ${TEST_URL}..."
echo "      Waiting for n8n service to become available (Timeout: ${VERIFY_TIMEOUT_SECONDS}s)."
SECONDS=0
while true; do
    # We curl 'localhost' as it's faster and avoids firewall issues if script is run locally
    if curl -s --fail "http://localhost:${TEST_PORT}" > /dev/null; then
        echo ""
        echo "############################################################"
        echo "### SUCCESS: Test instance is LIVE! ###"
        echo "############################################################"
        echo "  => Instance is accessible at: ${TEST_URL}"
        echo "  => (Verified via http://localhost:${TEST_PORT})"
        echo ""
        VERIFIED=true
        break
    fi
    
    if [ $SECONDS -ge $VERIFY_TIMEOUT_SECONDS ]; then
        echo ""
        echo "############################################################"
        echo "### FAILURE: Test instance FAILED to start. ###"
        echo "############################################################"
        echo "  => Could not connect to http://localhost:${TEST_PORT} after ${VERIFY_TIMEOUT_SECONDS}s."
        echo "  => Check logs with: cd ${EVAL_TEST_DIR} && docker compose -p ${TEST_PROJECT_NAME} logs -f n8n"
        echo ""
        VERIFIED=false
        break
    fi
    
    sleep $VERIFY_POLL_INTERVAL
    SECONDS=$((SECONDS + VERIFY_POLL_INTERVAL))
    echo -n "."
done

# 6. Interactive Cleanup
echo "[6/6] Cleanup"
if [ "$VERIFIED" = true ]; then
    echo "You can now manually verify the instance in your browser at ${TEST_URL}"
    echo "NOTE: If ${SERVER_IP} is not accessible, you may need to open the firewall:"
    echo "sudo ufw allow ${TEST_PORT}/tcp"
fi
echo ""
read -p "Press [Enter] to SHUT DOWN and DELETE the test environment..."

echo "      Shutting down and deleting test containers, volumes, and network..."
# We are already in the correct directory (EVAL_TEST_DIR)
docker compose -p "$TEST_PROJECT_NAME" -f docker-compose.yml -f docker-compose.test-override.yml down -v --remove-orphans
cd ~ # Go back home to delete the directory
rm -rf "$EVAL_TEST_DIR"

echo "### TEST RESTORE PROCESS COMPLETE. SYSTEM IS CLEAN. ###"

Étape 2 : Lancer le test

Exécutez le script en lui donnant le chemin complet vers votre fichier .tar.gz :

./test_restore.sh /opt/n8n_backups/votre_archive.tar.gz
Étape 3 : Vérification et Nettoyage

Le script va maintenant s’exécuter et automatiser tout le processus :

  1. Nettoyage : Il supprime tout ancien environnement de test (n8nrestored).
  2. Extraction : Il crée un dossier (~/n8n_restored_instance) et y extrait l’archive.
  3. Isolation : Il commente les container_name et crée un fichier docker-compose.test-override.yml pour changer le port et désactiver N8N_SECURE_COOKIE.
  4. Restauration : Il crée de nouveaux volumes Docker (préfixés n8nrestored_) et y copie les données.
  5. Démarrage : Il lance les conteneurs de test.
  6. Vérification : Il tente de contacter l’instance de test sur le nouveau port (ex: 5679).
    • En cas d’échec : Il affiche un message d’erreur et les commandes pour consulter les logs.
    • En cas de succès : Il confirme que l’instance est en ligne.
  7. Nettoyage interactif : Le script se met en pause et attend votre confirmation (touche [Entrée]).
    • À ce stade, vous pouvez vérifier manuellement l’URL http://<VOTRE_IP>:<PORT_TEST> dans votre navigateur (pensez à ouvrir le port sur votre pare-feu si nécessaire : sudo ufw allow 5679/tcp).
    • Une fois votre vérification manuelle terminée, appuyez sur [Entrée] dans le terminal. Le script supprimera alors automatiquement et proprement toutes les ressources de test (conteneurs, volumes, réseaux et le dossier d’extraction).

8. Gestion des Logs Docker

Par défaut, les logs Docker peuvent saturer votre disque. Il est recommandé de configurer la rotation des logs.

Créez ou modifiez le fichier /etc/docker/daemon.json :

sudo nano /etc/docker/daemon.json

Ajoutez le contenu suivant :

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Cela limite les logs de chaque conteneur à 3 fichiers de 10 Mo chacun. Redémarrez Docker pour appliquer les changements :

sudo systemctl restart docker

9. Accès et Finalisation

🎉 C’est terminé !

Ouvrez votre navigateur et rendez-vous sur votre URL (ex: https://n8n.exemple.com). Vous devriez voir l’interface n8n sécurisée par HTTPS. Créez votre compte « Owner » pour commencer à automatiser.

Vous avez déployé avec succès une pile d’automatisation complète, sécurisée et alignée sur les meilleures pratiques.

Publications similaires