
Docker sur NAS : architecture réseau et bonnes pratiques
Pourquoi Docker sur un NAS
Un NAS moderne est aussi serveur multimédia, gestionnaire de photos, agrégateur de téléchargements et dashboard domotique. Installer tout ça nativement sur Debian, c'est accumuler les conflits de dépendances. Docker isole chaque service dans son conteneur avec ses propres dépendances.
Reste à décider où stocker les données, comment isoler les réseaux, et comment empêcher Docker de contourner le firewall.
Installation : Docker CE, pas docker.io
Le paquet docker.io des dépôts Debian traîne toujours 2 à 3 versions en arrière. On passe par le dépôt officiel :
# 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
Le plugin Compose v2 remplace l'ancien docker-compose Python : la commande devient docker compose, sans tiret, et c'est nettement plus rapide.
Daemon.json : data root et durcissement
Par défaut, Docker stocke tout dans /var/lib/docker. Quand le système tourne sur une clé USB de 4 Go, il faut déplacer le data root sur le RAID Btrfs. Le reste du fichier concentre le durcissement :
{
"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
}
- data-root : tous les layers, volumes, images et conteneurs vivent sur
/mnt/data/docker, protégés par le RAID. - log-driver + log-opts : rotation automatique (10 Mo max, 3 fichiers). Sans ça, un conteneur bavard remplit le disque en deux jours.
- default-ulimits : augmente la limite de fichiers ouverts, utile aux services intensifs.
- userns-remap : les conteneurs tournent avec un mapping d'UID décalé, donc root dans le conteneur n'est jamais root sur l'hôte. Une vraie mitigation.
- no-new-privileges : les processus ne peuvent pas gagner de privilèges via setuid/setgid.
- live-restore : les conteneurs survivent à un redémarrage du daemon, 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
L'intérêt, c'est le blast radius. Si un conteneur est compromis, il ne peut communiquer qu'avec ses voisins immédiats : Radarr ne voit pas Immich, Grafana ne voit pas Jellyfin. Seul le reverse proxy est connecté à plusieurs réseaux pour router le trafic. L'équivalent de VLANs, mais pour les conteneurs.
Un conteneur peut être rattaché à plusieurs réseaux quand c'est nécessaire : Sonarr vit sur arr_net pour parler à Prowlarr et aux downloaders, et sur proxy_net pour être accessible via le reverse proxy.
Le piège UFW + Docker
Le piège classique : Docker manipule iptables directement, en contournant complètement UFW. Bloquer le port 8080 dans UFW ne sert à rien si un conteneur le publie — il sera ouvert.
La solution tient en deux volets. D'abord la chaîne DOCKER-USER, que Docker évalue avant ses propres règles :
# /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
Ensuite, la restriction de l'adresse d'écoute par défaut dans daemon.json :
{
"ip": "192.168.1.50"
}
Les ports publiés n'écoutent alors que sur l'IP LAN du NAS. Les deux combinés rendent les conteneurs accessibles uniquement depuis le réseau local, quoi que Docker fasse avec iptables.
Le pattern PUID/PGID
Les images linuxserver.io acceptent des variables d'environnement PUID et PGID au lieu de tourner en root :
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
1000:1000 correspondant à l'utilisateur nasadmin sur l'hôte, les fichiers créés par les conteneurs ont les bons propriétaires. Zéro galère de permissions.
Organisation des répertoires
/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 concernés.
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
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 doit être accessible via le reverse proxy. Prometheus et node-exporter restent confinés sur monitoring_net, volontairement.
Toggles Ansible
Chaque stack s'active ou se désactive via une variable :
# 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 ne déploie que les stacks activées. Passer docker_apps_photos_enabled: false et relancer le playbook stoppe les conteneurs et retire proprement les compose files.
Le résultat
Isolation réseau, daemon durci, stockage sur RAID, cohabitation avec le firewall : l'architecture demande du travail initial. Une fois en place, ajouter un service se résume à créer un compose file, le rattacher au bon réseau et activer le toggle Ansible. Le NAS devient une plateforme de services maintenable et sécurisée.
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
Articles similaires