Pourquoi consolider cinq services sur un seul VPS
L'approche « une app, un VPS » a une logique : isolation totale, déploiement indépendant, aucun risque de contention. Elle a aussi un coût réel — cinq serveurs, cinq adresses IP, cinq renouvellements, cinq configurations nginx, cinq certificats TLS à surveiller. Pour un stack personnel ou une petite équipe, ce coût n'est justifié que si les applications ont des charges de pointe très différentes ou des exigences de sécurité incompatibles.
Nextcloud, Vaultwarden, Jellyfin, Immich et Paperless-ngx ont en commun d'être des applications à trafic modéré, utilisées principalement par un ou quelques utilisateurs. Vaultwarden consomme moins de 50 Mo de RAM en mode veille. Paperless-ngx tourne autour de 200 Mo. Immich, le plus gourmand du lot hors transcodage, reste sous 400 Mo au repos. Ces mesures sont documentées dans les projets respectifs sur GitHub et dans les retours de la communauté auto-hébergement.
La consolidation sur un VPS ne supprime pas les risques — elle les concentre. La contrepartie est un seul point d'entrée à durcir, un seul certificat à gérer, et un manifeste Docker Compose versionné qui constitue à lui seul la documentation complète de l'infrastructure.
Ce que ce stack apporte concrètement
- Souveraineté sur les données — fichiers, mots de passe, photos et documents restent sur votre serveur, sous votre clé de chiffrement, sans dépendance à un fournisseur cloud tiers.
- Un seul certificat TLS wildcard — Caddy demande et renouvelle automatiquement
*.votre-domaine.comvia le défi DNS-01, couvrant tous les sous-domaines du stack en une seule configuration. - Réseau Docker interne isolé — aucune application n'expose de port public directement ; tout le trafic entrant passe par Caddy sur 80/443, et les services communiquent sur un réseau bridge privé.
- Volumes nommés et restauration prévisible — chaque service déclare ses données dans un volume Docker nommé (
nextcloud_data,vaultwarden_data…), ce qui rend les sauvegardes et les migrations reproductibles par une seule commandersync. - Mises à jour indépendantes — tirer une nouvelle image pour Immich ne redémarre ni Jellyfin ni Paperless-ngx ;
docker compose up -d --no-deps immichne touche que le service concerné. - Coût fixe et prévisible — un VPS à ressources fixes élimine les surprises de facturation à l'usage que génèrent les services cloud quand le transcodage Jellyfin s'emballe ou que les tâches OCR de Paperless-ngx s'accumulent.
- Maillage de services possible — Nextcloud peut utiliser Redis et MariaDB déjà présents dans le Compose ; Immich partage le même réseau que le reverse proxy sans configuration supplémentaire.
Prérequis : dimensionnement et ports
Le plancher réaliste pour ce stack en usage courant est 4 vCPU / 8 Go de RAM / 100 Go de stockage SSD. Ce dimensionnement couvre les empreintes au repos de chaque service et laisse une marge pour le transcodage Jellyfin à la demande et les tâches OCR de Paperless-ngx, qui sont les deux pics de charge non négligeables du stack.
Détail des empreintes mesurées en mode veille (aucune session active, aucune tâche en arrière-plan) :
- Nextcloud (PHP-FPM + cron) : ~300 Mo selon la charge de synchronisation.
- Vaultwarden : < 50 Mo, image Rust très compacte.
- Jellyfin : ~250 Mo sans transcodage actif. En transcodage soft (x264, 1080p) : 1 à 2 vCPU en pointe.
- Immich (server + microservices) : ~350-400 Mo au repos. Les tâches d'apprentissage automatique (détection de visages, classification) consomment jusqu'à 2 Go de RAM selon le volume.
- Paperless-ngx (web + worker) : ~200 Mo. L'OCR Tesseract sur un PDF de 50 pages peut saturer temporairement un vCPU.
- Caddy : < 30 Mo.
- Bases de données (MariaDB pour Nextcloud + Paperless, Redis) : ~200 Mo combinés.
Total estimé au repos : ~1,6 Go sur les 8 Go disponibles. La marge absorbe les pics et permet d'ajouter un service supplémentaire sans redimensionner.
Ports à ouvrir sur le pare-feu : 80/tcp et 443/tcp uniquement. Tous les autres ports restent fermés — les services internes ne sont pas exposés directement.
Déploiement pas à pas
Préparer le VPS
Connectez-vous en SSH à votre VPS et mettez à jour le système :
apt update && apt upgrade -y. Installez Docker et le plugin Compose :curl -fsSL https://get.docker.com | sh. Vérifiez l'installation :docker compose version. Créez un utilisateur non-root dédié et ajoutez-le au groupedocker:adduser deploy && usermod -aG docker deploy. Activez UFW avec les règles minimales :ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw enable.Configurer le DNS
Dans votre zone DNS, créez un enregistrement A pour le domaine racine pointant vers l'IP de votre VPS, puis des sous-domaines CNAME ou A pour chaque service :
nextcloud.votre-domaine.com,vault.votre-domaine.com,jellyfin.votre-domaine.com,photos.votre-domaine.cometdocs.votre-domaine.com. Si vous utilisez le défi DNS-01 de Caddy pour le certificat wildcard, assurez-vous que votre fournisseur DNS dispose d'une API supportée par le modulecaddy-dnscorrespondant. Attendez la propagation DNS (quelques minutes à quelques heures selon le TTL configuré).Créer la structure de dossiers
Créez l'arborescence du projet sur le VPS :
mkdir -p /opt/homelab/{caddy,nextcloud,vaultwarden,jellyfin,immich,paperless}. Ce répertoire contiendra le fichierdocker-compose.yml, leCaddyfileet les fichiers d'environnement. Les données persistantes seront stockées dans des volumes Docker nommés, pas dans des bind-mounts, pour simplifier les sauvegardes et éviter les problèmes de permissions.Écrire le Caddyfile
Dans
/opt/homelab/caddy/Caddyfile, déclarez un bloc par sous-domaine. Exemple pour Nextcloud :nextcloud.votre-domaine.com { reverse_proxy nextcloud:80 }. Répétez le motif pour chaque service en pointant vers le nom de service Docker (vaultwarden,jellyfin,immich-server,paperless-webserver). Caddy obtient et renouvelle les certificats Let's Encrypt automatiquement au premier démarrage. Pour un certificat wildcard, remplacez les blocs individuels par*.votre-domaine.comavec le module DNS de votre fournisseur, configuré via les variables d'environnement de l'image Caddy personnalisée.Écrire le docker-compose.yml
Créez
/opt/homelab/docker-compose.ymlavec un réseauproxypartagé et un réseauinternalisolé. Déclarez le service Caddy avecports: ["80:80", "443:443"]etvolumes: ["./caddy/Caddyfile:/etc/caddy/Caddyfile", "caddy_data:/data"]. Pour chaque application, déclareznetworks: [proxy, internal]et n'exposez aucun portports:— seul Caddy expose des ports publics. Utilisezdepends_onaveccondition: service_healthypour que Nextcloud n'essaie pas de rejoindre MariaDB avant qu'elle soit prête. Les variables sensibles (mots de passe de bases de données, clés secrètes) passent dans un fichier.envréférencé parenv_file: .env.Configurer les variables d'environnement
Créez
/opt/homelab/.envavec les variables requises par chaque service :MYSQL_ROOT_PASSWORD,MYSQL_DATABASE,MYSQL_USER,MYSQL_PASSWORDpour MariaDB,NEXTCLOUD_ADMIN_USER,NEXTCLOUD_ADMIN_PASSWORD,NEXTCLOUD_TRUSTED_DOMAINSpour Nextcloud,VAULTWARDEN_ADMIN_TOKENpour Vaultwarden. Générez les secrets avecopenssl rand -hex 32. Pour Immich, copiez le fichier.envd'exemple depuis le dépôt officiel — il déclare les variables requises et leurs valeurs par défaut. Ne commitez jamais ce fichier dans un dépôt public ; ajoutez.envà votre.gitignore.Lancer le stack
Depuis
/opt/homelab, lancezdocker compose pullpour télécharger toutes les images, puisdocker compose up -dpour démarrer l'ensemble. Suivez les logs au démarrage avecdocker compose logs -fpour vous assurer que chaque service démarre sans erreur. La première initialisation de Nextcloud et Paperless-ngx peut prendre quelques minutes (migrations de base de données, génération des clés). Caddy obtient les certificats TLS lors du premier accès à chaque sous-domaine — vérifiez que les ports 80 et 443 sont accessibles depuis l'extérieur avant de tester.Finaliser la configuration de chaque service
Accédez à chaque interface web pour finaliser la configuration initiale : Nextcloud (
nextcloud.votre-domaine.com) pour activer les applications recommandées (Contacts, Calendrier, Talk) ; Immich (photos.votre-domaine.com) pour configurer les bibliothèques et activer les tâches d'apprentissage automatique en arrière-plan ; Paperless-ngx (docs.votre-domaine.com) pour configurer le consommateur de documents et l'OCR ; Jellyfin (jellyfin.votre-domaine.com) pour pointer vers les dossiers de médias montés en volume. Vaultwarden (vault.votre-domaine.com) ne nécessite qu'une création de compte depuis le client Bitwarden — aucune configuration serveur initiale.
Caddy, Nginx Proxy Manager ou Traefik : quel reverse proxy pour ce stack
Faites défiler le tableau
| Critère | Caddy | Nginx Proxy Manager | Traefik |
|---|---|---|---|
| Configuration | Caddyfile déclaratif, rechargement sans downtime | Interface web, aucun fichier à éditer | Labels Docker, recharge automatiquement |
| SSL automatique | Intégré, DNS-01 et HTTP-01 natifs | Let's Encrypt via l'interface, DNS-01 possible | Let's Encrypt via ACME resolver, DNS-01 via providers |
| Wildcard | Natif via module caddy-dns | Possible mais demande configuration manuelle | Natif via certificateResolvers |
| Courbe d'apprentissage | Faible — Caddyfile lisible en 10 minutes | Très faible — tout se fait en clics | Moyenne — documentation volumineuse |
| Adapté à ce stack | Oui — configuration versionnée, rechargement en ligne | Oui pour débuter, moins idéal pour versionner | Oui pour les stacks plus complexes, surcoût de config ici |
Durcissement minimal avant exposition publique
Quatre mesures à appliquer avant de rendre le stack accessible depuis l'extérieur :
1. Désactivez la page d'inscription ouverte de Vaultwarden en posant SIGNUPS_ALLOWED=false dans le .env une fois votre compte créé.
2. Ajoutez un en-tête X-Robots-Tag: noindex dans le Caddyfile pour Vaultwarden et l'interface d'administration de Paperless-ngx — ces pages n'ont pas à être indexées.
3. Activez la rotation des journaux Docker (log-opts dans /etc/docker/daemon.json) pour éviter que les logs de Jellyfin ou de Paperless-ngx ne saturent le disque.
4. Planifiez un docker compose pull && docker compose up -d hebdomadaire via cron pour maintenir les images à jour — les applications self-hosted publient régulièrement des correctifs de sécurité. Vérifiez les changelogs avant de mettre à jour Immich, qui peut introduire des migrations de base de données non réversibles.
Dépannage : erreurs courantes au démarrage
Error response from daemon: network proxy declared as external, but could not be found — Ce message apparaît quand le réseau Docker externe déclaré dans docker-compose.yml n'existe pas encore. Créez-le manuellement avant le premier docker compose up : docker network create proxy. Ou passez le réseau en interne au Compose (retirez external: true) pour que Compose le crée lui-même.
nextcloud.votre-domaine.com redirected you too many times — Nextcloud détecte la requête HTTPS comme HTTP car Caddy la retransmet en HTTP sur le réseau interne. Ajoutez NEXTCLOUD_TRUSTED_PROXIES avec le sous-réseau Docker (ex. 172.16.0.0/12) et OVERWRITEPROTOCOL=https dans le .env. Sans ces variables, Nextcloud ne fait pas confiance aux en-têtes X-Forwarded-Proto transmis par Caddy et tente de rediriger vers HTTPS indéfiniment.
Immich cannot connect to database: connection refused — Immich démarre avant que PostgreSQL soit prêt. Ajoutez depends_on avec condition: service_healthy sur le service immich-server et vérifiez que le service database déclare un healthcheck valide (ex. pg_isready -U immich). Sans healthcheck, Docker Compose considère le service « démarré » dès que le conteneur est lancé, pas dès qu'il accepte des connexions.
Paperless-ngx worker exited with error: celery worker unhealthy — La base Redis n'est pas accessible. Vérifiez que le service Redis est bien dans le même réseau que Paperless et que la variable PAPERLESS_REDIS pointe vers le nom de service Compose (redis://redis:6379), pas vers localhost — dans Docker Compose, localhost dans un conteneur pointe vers le conteneur lui-même, pas vers un autre service.
Caddy ne renouvelle pas le certificat wildcard — Si vous utilisez le défi DNS-01, vérifiez que les variables d'environnement de l'API DNS (token, zone ID) sont bien transmises au service Caddy dans le Compose. Un token expiré ou une permission DNS insuffisante fait échouer silencieusement le renouvellement 30 jours avant l'expiration — Caddy enregistre l'erreur dans ses logs mais ne bloque pas le trafic jusqu'à l'expiration effective.
Pour aller plus loin
Ce stack couvre les cinq services les plus demandés dans les configurations homelab 2026. Chacun dispose d'un guide dédié sur ce blog si vous souhaitez approfondir un aspect particulier : sauvegarde Nextcloud, gestion des albums Immich, transcodage matériel Jellyfin, règles de rétention Paperless-ngx ou synchronisation multi-appareils Vaultwarden.
Pour aller au-delà de ce stack, les prochaines briques habituellement ajoutées sont Uptime Kuma (monitoring des services internes) et Ntfy ou Apprise (notifications push). Ces services sont suffisamment légers pour s'ajouter au même Compose sans impacter le dimensionnement.
Si la gestion manuelle des mises à jour et des sauvegardes devient une charge, Coolify et Dokploy offrent des interfaces qui automatisent ces tâches tout en conservant l'architecture Docker Compose sous-jacente — voir le guide de migration Heroku/Vercel vers VPS pour le contexte.