Fransys

Blog technique — Architecture, Cloud & DevOps

BlogServicesContactÀ propos

Suivez-moi

githubGitHublinkedinLinkedinmailMail

© 2026 Fransys • Fransys

Fransys

Catégories

  • Tous les articles
  • Tags
  • productivite10
  • nas10
  • ia8
  • securite7
  • linux6
  • claude-code6
  • auto-hebergement6
  • neovim5
  • docker5
  • editeur4
  • mcp3
  • vpn3
  • reseau3
  • lua2
  • terminal2
nasdockerauto-hébergementréseau

Docker sur NAS : architecture réseau et bonnes pratiques

Publié le
10 février 2026·7 min de lecture
Avatar François GUERLEZFrançois GUERLEZ

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 précédent

← Telescope, Treesitter et les plugins essentiels pour coder dans Neovim

Article suivant

Terminal, Git et recherche globale intégrés dans Neovim→
← Retour au blog

Sommaire

  • Pourquoi Docker sur un NAS
  • Installation : Docker CE, pas docker.io
  • Data root sur le RAID
  • Daemon.json durci
  • Architecture réseau : isolation par stack
  • Le piège UFW + Docker
  • 1. La chaîne DOCKER-USER
  • 2. Restriction dans daemon.json
  • Le pattern PUID/PGID
  • Organisation des répertoires
  • Organisation des Compose files
  • Toggles Ansible
  • Le résultat