HOMELAB • DOCKER • WIKI.JS • POSTGRESQL
Déployons un wiki privé sur une VM Debian 13, puis sécurisons son accès et construisons une vraie chaîne de sauvegarde : PostgreSQL, contrôle de restauration, copie SFTP et rétention sur NAS Synology.
Tutoriel basé sur des manipulations réalisées et testées en laboratoire — adapter les adresses IP, comptes et chemins à son environnement.
1. Objectifs et prérequis
Wiki.js est un outil de documentation collaborative, adapté aux procédures techniques, inventaires et bases de connaissances. Dans ce laboratoire, l’objectif est une installation privée, reproductible et maintenable, sur une VM Debian disposant déjà de Docker.
Debian 13, Docker Engine et Docker Compose, accès SSH, espace disque suffisant.
Wiki.js 2 et PostgreSQL 17, conteneurs séparés et réseau Docker dédié.
Export PostgreSQL quotidien, copies locales et NAS Synology via SFTP.
Exemples d’adresses du laboratoire : 192.168.1.239 pour Debian et 192.168.1.241 pour Synology. Sur un autre réseau, remplacez-les par vos propres adresses.
2. Architecture retenue
L’application expose seulement son port web. PostgreSQL reste accessible via le réseau Docker interne ; un volume nommé assure sa persistance. La sauvegarde utilise pg_dump, puis une seconde copie est transférée par SFTP.
Un volume Docker protège les données lors du remplacement d’un conteneur, mais ne remplace pas une sauvegarde. La base est exportée séparément et sa restauration doit être vérifiée.
3. Nettoyer une ancienne installation (facultatif)
Cette étape est destructrice et ne concerne que les environnements possédant une ancienne installation Wiki.js à abandonner. Sauvegardez les données avant toute suppression et contrôlez les montages utilisés par les autres conteneurs.
docker ps -a --filter "name=^/wikijs$"
docker inspect wikijs --format '{{json .Mounts}}'
docker ps -aq | xargs -r docker inspect --format '{{.Name}} {{range .Mounts}}{{.Source}} {{end}}'
Après vérification qu’aucune autre application n’utilise le dossier, l’ancienne installation du laboratoire a été supprimée :
docker stop wikijs
docker rm wikijs
sudo rm -rf -- /srv/appdata/wiki

4. Préparer le projet et le fichier Compose
Créer les dossiers
sudo mkdir -p /opt/stacks/wikijs/backups
sudo chown -R "$USER:$(id -gn)" /opt/stacks/wikijs
chmod 750 /opt/stacks/wikijs
cd /opt/stacks/wikijs
Isoler les paramètres sensibles
Le fichier .env conserve les paramètres de connexion. Son contenu ne doit pas être publié avec le tutoriel ni versionné dans Git.
umask 077
printf 'POSTGRES_DB=wiki\nPOSTGRES_USER=wikijs\nPOSTGRES_PASSWORD=%s\nWIKI_PORT=3015\n' "$(openssl rand -hex 32)" > .env
chmod 600 .env
printf '.env\nbackups/\n' > .gitignore

Le fichier compose.yaml
Les paramètres suivants correspondent à l’architecture validée dans le laboratoire : démarrage de Wiki.js seulement lorsque PostgreSQL est sain, volume nommé et base non publiée sur le LAN.
services:
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- wiki_backend
healthcheck:
test:
- CMD-SHELL
- pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
wiki:
image: ghcr.io/requarks/wiki:2
restart: unless-stopped
init: true
depends_on:
db:
condition: service_healthy
environment:
DB_TYPE: postgres
DB_HOST: db
DB_PORT: 5432
DB_USER: ${POSTGRES_USER}
DB_PASS: ${POSTGRES_PASSWORD}
DB_NAME: ${POSTGRES_DB}
ports:
- "${WIKI_PORT}:3000"
networks:
- wiki_backend
volumes:
postgres_data:
networks:
wiki_backend:
driver: bridge
Le port 3015 est publié sur toutes les interfaces de l’hôte ; protégez l’accès au moyen du pare-feu et ne créez aucune redirection publique depuis votre routeur. Pour limiter l’écoute à une IP donnée, vous pouvez utiliser 192.168.1.239:3015:3000 dans ports. Les tags d’images sont fixés sur une version majeure, mais il faut aussi piloter les mises à jour des correctifs.
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=30 wiki


5. Initialiser et franciser Wiki.js
Dans le navigateur, ouvrir http://192.168.1.239:3015, puis définir l’adresse du site, créer un compte administrateur avec un mot de passe robuste et terminer l’assistant initial. Dans Administration → Locale, installer/activer le français selon les options proposées. Le Wiki.js du laboratoire était ensuite disponible en français.
La création du compte administrateur n’a pas été capturée. Pour éviter une fausse illustration présentée comme authentique, ce tutoriel s’appuie sur les captures réellement effectuées à partir des étapes suivantes.
6. Bloquer la lecture anonyme
Dans Administration → Groupes → Guests, examiner à la fois les permissions globales et les Page Rules. Une autorisation Path Starts With / peut exposer toutes les pages. Supprimer la règle générale non souhaitée et enregistrer les changements.


Vérifier le refus depuis une session anonyme
Créer une page de test, copier son adresse et l’ouvrir dans une fenêtre de navigation privée. Le laboratoire affiche « Non autorisé » : la lecture anonyme de cette page est bien bloquée.

7. Sauvegarder PostgreSQL localement
Exporter une première archive
Le format personnalisé PostgreSQL (-Fc) permet de restaurer via pg_restore. Limiter les permissions du dossier et des dumps.
cd /opt/stacks/wikijs
umask 077
docker compose exec -T db pg_dump -U wikijs -d wiki -Fc > backups/wikijs-test.dump
docker compose exec -T db pg_restore -l < backups/wikijs-test.dump
ls -lh backups/
Script backup.sh
Il interrompt son exécution en cas d’erreur, prend un verrou, écrit d’abord dans un fichier temporaire, contrôle le catalogue de l’archive, puis applique une rétention locale d’environ sept jours.
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
PROJECT_DIR="/opt/stacks/wikijs"
BACKUP_DIR="$PROJECT_DIR/backups"
cd "$PROJECT_DIR"
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
exec 9>"$PROJECT_DIR/.backup.lock"
flock -n 9 || { echo "Une sauvegarde est déjà en cours"; exit 1; }
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP="$BACKUP_DIR/wikijs_${DATE}.dump"
TEMP="${BACKUP}.tmp"
trap 'rm -f -- "$TEMP"' EXIT
echo "[$(date)] Début sauvegarde Wiki.js"
docker compose exec -T db pg_dump -U wikijs -d wiki -Fc > "$TEMP"
test -s "$TEMP"
docker compose exec -T db pg_restore -l < "$TEMP" > /dev/null
mv -- "$TEMP" "$BACKUP"
find "$BACKUP_DIR" -maxdepth 1 -type f -name 'wikijs_*.dump' -mtime +6 -delete
echo "[$(date)] Sauvegarde terminée : $BACKUP"
chmod 700 backup.sh
./backup.sh
ls -lh backups/

8. Tester une restauration sans écraser la production
Créer une base indépendante, restaurer le dump et comparer les nombres d’enregistrements. Le test du laboratoire a retrouvé les tables de Wiki.js puis les mêmes nombres de pages, utilisateurs et groupes que dans la base principale.
docker compose exec -T db createdb -U wikijs wiki_restore_test
docker compose exec -T db pg_restore -U wikijs -d wiki_restore_test --no-owner --no-acl < backups/wikijs-test.dump
docker compose exec -T db psql -U wikijs -d wiki_restore_test -c "\dt"
docker compose exec -T db psql -U wikijs -d wiki_restore_test -c "SELECT (SELECT count(*) FROM pages) AS pages, (SELECT count(*) FROM users) AS users, (SELECT count(*) FROM groups) AS groups;"
docker compose exec -T db psql -U wikijs -d wiki -c "SELECT (SELECT count(*) FROM pages) AS pages, (SELECT count(*) FROM users) AS users, (SELECT count(*) FROM groups) AS groups;"
# Après validation uniquement :
docker compose exec -T db dropdb -U wikijs wiki_restore_test


Un dump dont le catalogue est lisible n’est pas une preuve de restauration. Tester périodiquement le processus entier, idéalement sur un environnement isolé.
9. Externaliser les sauvegardes vers Synology
Un partage DSM docker-backups a été créé sur le NAS 192.168.1.241, avec un compte non administrateur svc_wikijs_backup. Après exploration de NFS, l’équipe du laboratoire a retenu SFTP afin d’éviter le montage permanent et les correspondances UID/GID. Sur DSM, activer le service SFTP, sans exposer le port SSH à Internet.
Créer une clé dédiée sur Debian
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_wikijs_backup -C "wikijs-backup"
chmod 600 ~/.ssh/id_ed25519_wikijs_backup
chmod 644 ~/.ssh/id_ed25519_wikijs_backup.pub
Installer uniquement la clé publique dans le authorized_keys du compte dédié sur Synology, avec un répertoire .ssh en mode 700 et un fichier authorized_keys en mode 600. La clé privée demeure exclusivement sur Debian. Pour une automatisation sans interaction, la clé peut être dépourvue de phrase secrète, mais doit être strictement protégée.
sftp -o BatchMode=yes -o IdentitiesOnly=yes \
-i ~/.ssh/id_ed25519_wikijs_backup \
svc_wikijs_backup@192.168.1.241

Script de transfert sync-nas.sh
Le script repère le dump le plus récent et le transfère vers le dossier NAS. Il ne détruit aucune archive distante. Le téléchargement de vérification est montré ensuite.
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
BACKUP_DIR="/opt/stacks/wikijs/backups"
NAS_USER="svc_wikijs_backup"
NAS_HOST="192.168.1.241"
NAS_DIR="/docker-backups"
SSH_KEY="/home/laurent/.ssh/id_ed25519_wikijs_backup"
BACKUP=$(find "$BACKUP_DIR" -maxdepth 1 -type f -name 'wikijs_*.dump' \
-printf '%T@ %p\n' | sort -nr | head -n 1 | cut -d' ' -f2-)
[[ -n "$BACKUP" ]] || { echo "Aucune sauvegarde"; exit 1; }
FILENAME=$(basename "$BACKUP")
sftp -o BatchMode=yes -o IdentitiesOnly=yes -o ConnectTimeout=15 \
-i "$SSH_KEY" -b - "${NAS_USER}@${NAS_HOST}" <<EOF
cd $NAS_DIR
put $BACKUP $FILENAME
EOF
echo "Transfert terminé : $FILENAME"
chmod 700 sync-nas.sh
./sync-nas.sh
Vérifier l’intégrité avec SHA-256
Récupérer un dump distant dans un répertoire temporaire puis comparer ses empreintes locales et distantes. Dans le laboratoire, elles étaient identiques.
mkdir -p /tmp/wikijs-verification
chmod 700 /tmp/wikijs-verification
sftp -o BatchMode=yes -o IdentitiesOnly=yes \
-i ~/.ssh/id_ed25519_wikijs_backup \
svc_wikijs_backup@192.168.1.241 <<'EOF'
get /docker-backups/wikijs_20261020_151352.dump /tmp/wikijs-verification/wikijs_20261020_151352.dump
bye
EOF
sha256sum /opt/stacks/wikijs/backups/wikijs_20261020_151352.dump \
/tmp/wikijs-verification/wikijs_20261020_151352.dump

Le fichier distant présentait des bits de permissions très permissifs, tandis que les ACL héritées du partage DSM autorisaient le compte de service et les administrateurs, avec refus explicite de guest. Les droits effectifs doivent être testés sur le NAS avec un autre compte non privilégié. Le chiffrement eCryptfs protège les données au repos lorsque le partage est démonté, pas contre l’effacement par un utilisateur autorisé.

10. Planifier sauvegardes, rétention et journaux
Chaîner les opérations à 02:00
Créer une seule tâche dans la crontab du compte Debian exécutant Docker. Le transfert ne démarre que si la sauvegarde locale se termine correctement.
crontab -e
# À insérer dans la crontab :
0 2 * * * { /opt/stacks/wikijs/backup.sh && /opt/stacks/wikijs/sync-nas.sh; } >> /opt/stacks/wikijs/backups/backup.log 2>&1

Rotation des dumps sur le NAS
Le NAS du laboratoire utilise un volume ext4 et un partage eCryptfs ; Snapshot Replication sur Btrfs n’est donc pas disponible pour ce partage. Un script local au NAS réalise la rotation après vérification du montage chiffré.
#!/bin/sh
set -eu
BACKUP_DIR="/volume1/docker-backups"
LOG_FILE="/var/log/rotate-wikijs-backups.log"
if ! mount | grep -F " on $BACKUP_DIR type ecryptfs " > /dev/null; then
echo "$(date) ERREUR : partage chiffré non monté" >&2
exit 1
fi
[ -d "$BACKUP_DIR" ] || exit 1
echo "$(date) Début de rotation" >> "$LOG_FILE"
find "$BACKUP_DIR" -maxdepth 1 -type f -name 'wikijs_*.dump' \
-mtime +29 -exec sh -c '
for file do echo "$(date) Suppression : $file"; rm -- "$file"; done
' sh {} + >> "$LOG_FILE" 2>&1
echo "$(date) Rotation terminée" >> "$LOG_FILE"
Installer ce script sur Synology dans /usr/local/etc/scripts/rotate-wikijs-backups.sh avec les droits 700, puis créer dans Planificateur de tâches DSM une tâche quotidienne à 04:00. Une sélection de fichiers avec -print doit être testée avant d’autoriser leur suppression.

Rotation de backup.log sur Debian
Dans /etc/logrotate.d/wikijs-backup :
/opt/stacks/wikijs/backups/backup.log {
weekly
rotate 4
compress
delaycompress
missingok
notifempty
su laurent laurent
create 0600 laurent laurent
}
sudo chmod 600 /opt/stacks/wikijs/backups/backup.log
sudo /usr/sbin/logrotate -d /etc/logrotate.d/wikijs-backup
systemctl status logrotate.timer --no-pager


11. Bonnes pratiques et limites
- Wiki.js et PostgreSQL démarrent et communiquent.
- La page de test est inaccessible sans authentification.
- Un dump se restaure dans une base distincte et les données sélectionnées correspondent.
- Un fichier transféré sur Synology a été récupéré avec une empreinte SHA-256 identique.
- La chaîne complète a été testée manuellement ; cron, DSM et logrotate sont configurés.
- Contrôler la première exécution automatique et les fichiers générés après la nuit.
- Vérifier une suppression de rotation sur un fichier fictif ancien avant de compter dessus.
- Prévoir une copie indépendante et non modifiable par le compte de transfert.
- Empêcher l’accès public au port Wiki.js et envisager HTTPS si l’outil doit sortir du réseau privé.
- Mettre à jour les images Docker de manière contrôlée, en sauvegardant avant les changements.
- Les variables d’environnement peuvent être inspectées par les administrateurs Docker : protéger l’accès au daemon.
12. Conclusion
Nous disposons désormais d’un wiki privé sur Debian 13, reposant sur Docker Compose et PostgreSQL, accompagné d’une chaîne de sauvegarde locale et d’une copie SFTP vers Synology. Les captures de ce tutoriel correspondent aux tests effectués, notamment la restauration et le contrôle d’intégrité.
La prochaine amélioration logique sera d’ajouter une copie indépendante ou protégée contre l’effacement, puis de documenter un scénario complet de reprise après perte de la VM.
Un tutoriel d’installation devient une procédure d’exploitation lorsqu’il décrit aussi la supervision des erreurs, les permissions, les sauvegardes et les tests de restauration.
