Docker sur NAS : architecture réseau et bonnes pratiques

Docker sur NAS : architecture réseau et bonnes pratiques

·7 min de lecture·Mis à jour le 10 février 2026

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

PartagerLinkedInXBluesky

Articles similaires