Docker sur NAS : architecture réseau et bonnes pratiques
- Publié le
- ·7 min de lecture
Pourquoi Docker sur un NAS
Un NAS, ce n'est plus juste un serveur de fichiers depuis longtemps. C'est un serveur multimédia. Un gestionnaire de photos. Un agrégateur de téléchargements. Un dashboard domotique. Installer tout ça nativement sur Debian, c'est transformer la machine en usine à gaz avec des conflits de dépendances partout. Docker ? C'est l'isolation. Chaque service dans son conteneur, ses dépendances, zéro conflits.
Mais deployer Docker sur un NAS, ça demande du bon sens : où stocker les données ? Comment isoler les réseaux ? Comment empêcher Docker de contourner le firewall ? Voilà l'architecture que j'ai mise en place, et pourquoi chaque choix a du sens.
Installation : Docker CE, pas docker.io
Réflexe numéro un : ne pas installer le paquet docker.io des dépôts Debian. Ce paquet traine toujours 2-3 versions en arrière. On va chercher le dépôt officiel Docker CE :
# Ajout du dépôt Docker CE
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker.gpg] \
https://download.docker.com/linux/debian trixie stable" \
> /etc/apt/sources.list.d/docker.list
apt update
apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
On installe aussi le plugin Compose v2 (pas l'ancien docker-compose en Python). La commande devient docker compose (sans tiret). C'est plus rapide, plus moderne.
Data root sur le RAID
Par défaut, Docker stocke tout dans /var/lib/docker. Sur mon NAS, le système tourne sur une clé USB de 4 Go. Clairement pas la place de stocker des images Docker dedans. On déplace le data root sur le RAID Btrfs :
// /etc/docker/daemon.json (extrait)
{
"data-root": "/mnt/data/docker"
}
Tous les layers, les volumes, les images, les conteneurs - tout vit maintenant sur /mnt/data/docker. Le RAID prend soin du reste.
Daemon.json durci
Le fichier daemon.json, c'est le tableau de bord global de Docker. Voici ma configuration, with comments:
{
"data-root": "/mnt/data/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 65536
}
},
"userns-remap": "default",
"no-new-privileges": true,
"storage-driver": "overlay2",
"live-restore": true
}
Concrètement, ce que ça fait :
- log-driver + log-opts : rotation automatique des logs (10 Mo max, 3 fichiers, puis suppression). Sans ça, un conteneur bavard remplit le disque en deux jours. J'ai appris ça à mes dépens.
- default-ulimits : augmente la limite de fichiers ouverts. Les services intensifs disent merci.
- userns-remap : les conteneurs tournent avec un mapping d'UID décalé. Root dans le conteneur n'est jamais root sur l'hôte. C'est subtle mais c'est une vraie mitigation d'attaque.
- no-new-privileges : les processus dans les conteneurs ne peuvent pas obtenir des privilèges supplémentaires via setuid/setgid.
- live-restore : si le daemon Docker redémarre, les conteneurs continuent de tourner. Pratique pour les mises à jour sans interruption.
Architecture réseau : isolation par stack
Au lieu de tout mettre sur le réseau bridge par défaut, on crée des réseaux Docker séparés par groupe de services :
docker network create proxy_net
docker network create arr_net
docker network create photos_net
docker network create monitoring_net
docker network create auth_net
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a1b2c3d4e5f6 bridge bridge local
f6e5d4c3b2a1 host host local
1a2b3c4d5e6f none null local
7f8e9d0c1b2a proxy_net bridge local
3c4d5e6f7a8b arr_net bridge local
9a0b1c2d3e4f photos_net bridge local
5e6f7a8b9c0d monitoring_net bridge local
2d3e4f5a6b7c auth_net bridge local
Pourquoi se donner ce mal ? C'est le blast radius. Si un conteneur est compromis, il ne peut communiquer qu'avec ses voisins. Radarr ne voit pas Immich. Grafana ne voit pas Jellyfin. Seul le reverse proxy (Traefik ou Caddy) est connecté à plusieurs réseaux pour router le trafic. C'est un peu comme des VLANs, mais pour les conteneurs.
Un conteneur peut être connecté à plusieurs réseaux quand c'est nécessaire. Sonarr, par exemple, vit sur arr_net pour parler à Prowlarr et les downloaders, mais aussi sur proxy_net pour être accessible via le reverse proxy.
Le piège UFW + Docker
C'est THE piège classique, et il te mord tout le monde au moins une fois (moi deux fois). Docker manipule iptables directement, en contournant complètement UFW. Tu as beau bloquer le port 8080 dans UFW, si un conteneur le publie, bam, il est ouvert. C'est infuriating.
La solution propre en deux volets :
1. La chaîne DOCKER-USER
Docker crée une chaîne iptables spéciale DOCKER-USER qui s'évalue avant ses propres règles. C'est là qu'on ajoute nos restrictions :
# /etc/ufw/after.rules (à la fin du fichier)
*filter
:DOCKER-USER - [0:0]
# Autoriser uniquement le LAN à accéder aux ports Docker
-A DOCKER-USER -s 192.168.1.0/24 -j ACCEPT
-A DOCKER-USER -s 172.16.0.0/12 -j ACCEPT
-A DOCKER-USER -j DROP
COMMIT
2. Restriction dans daemon.json
On peut aussi restreindre l'adresse d'écoute par défaut :
{
"ip": "192.168.1.50"
}
Les ports publiés n'écoutent maintenant que sur l'IP LAN du NAS, pas sur toutes les interfaces.
Combiner les deux ? Les conteneurs sont accessibles uniquement depuis le réseau local, peu importe ce que Docker fait avec iptables.
Le pattern PUID/PGID
La majorité de mes conteneurs viennent de linuxserver.io. Ces images utilisent un pattern élégant : au lieu de tourner en root, elles acceptent des variables d'environnement PUID et PGID :
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
1000:1000 correspond à mon utilisateur nasadmin sur l'hôte. Résultat ? Les fichiers créés par les conteneurs ont les bons propriétaires. Zéro galère de permissions. C'est une chose simple qui économise des heures de debug.
Organisation des répertoires
J'ai adopté une structure claire sur le RAID :
/mnt/data/
├── apps/
│ ├── traefik/config/
│ ├── authelia/config/
│ ├── jellyfin/config/
│ ├── immich/config/
│ ├── sonarr/config/
│ ├── radarr/config/
│ ├── prowlarr/config/
│ └── grafana/config/
├── media/
│ ├── movies/
│ ├── tv/
│ └── music/
├── downloads/
│ ├── complete/
│ └── incomplete/
├── photos/
└── docker/ # Docker data-root
Chaque service a son répertoire sous /mnt/data/apps/<service>/config. Les données partagées (media, downloads, photos) vivent dans des répertoires communs montés en bind dans les conteneurs qui en ont besoin. C'est clean, c'est prévisible.
Organisation des Compose files
Un docker-compose.yml par stack fonctionnelle :
compose/
├── auth/ # Authelia, LLDAP
├── media/ # Jellyfin
├── arr/ # Sonarr, Radarr, Prowlarr, qBittorrent
├── photos/ # Immich
├── files/ # Syncthing, Samba
├── monitoring/ # Grafana, Prometheus, node-exporter
└── homelab/ # Homepage, Uptime Kuma
Voici un exemple pour le monitoring :
# compose/monitoring/docker-compose.yml
services:
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
volumes:
- /mnt/data/apps/grafana/config:/var/lib/grafana
networks:
- monitoring_net
- proxy_net
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
volumes:
- /mnt/data/apps/prometheus/config:/etc/prometheus
- prometheus_data:/prometheus
networks:
- monitoring_net
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
networks:
- monitoring_net
networks:
monitoring_net:
external: true
proxy_net:
external: true
volumes:
prometheus_data:
Grafana est le seul sur proxy_net parce qu'il a besoin d'être accessible via le reverse proxy. Prometheus et node-exporter ? Confinés sur monitoring_net. C'est volontaire.
Toggles Ansible
Chaque stack peut être activée ou désactivée via Ansible :
# group_vars/nas.yml
docker_apps_auth_enabled: true
docker_apps_media_enabled: true
docker_apps_arr_enabled: true
docker_apps_photos_enabled: true
docker_apps_files_enabled: true
docker_apps_monitoring_enabled: true
docker_apps_homelab_enabled: true
Le rôle Ansible déploie uniquement les stacks activées. Je veux temporairement désactiver Immich ? Je passe docker_apps_photos_enabled: false et je relance le playbook. Les conteneurs sont stoppés, les compose files retirés proprement. Pas de bordel.
Le résultat
Docker sur un NAS, ça ne s'improvise pas. Isolation réseau, daemon durci, stockage sur RAID, cohabitation avec le firewall - tout ça demande du travail initial. Une fois l'architecture en place, ajouter un nouveau service c'est : créer un compose file, le rattacher au bon réseau, fliper le toggle Ansible. Le NAS devient une vraie plateforme de services, maintenable, sécurisée, et pas un bordel de dépendances partout.
Série NAS Debian from scratch — Cet article fait partie d'une série complète sur la construction d'un NAS Debian.
Précédent : Installer Debian sur un NAS en mode 100% automatique | Suivant : Pare-feu et Fail2ban : verrouiller les accès réseau du NAS
Article suivant
Terminal, Git et recherche globale intégrés dans Neovim→