Buongiorno, Agente.
Da oltre un anno fai girare Docker dentro container LXC su Proxmox. Dieci container. Dieci piccole fortezze, ognuna con il suo stack Docker Compose. Immich, Frigate, Jellyfin, l’impero degli Arr. Separazione netta. Backup indipendenti. Sembra giusto.
Poi qualcuno ti lancia in faccia un thread di Reddit: “Docker in LXC è un antipattern.”
È ora di fare un audit dell’operazione.
L’Architettura Sotto Esame
Ecco com’è strutturato il Minilab. Ogni servizio ha il suo container LXC su Proxmox, e dentro ognuno gira Docker con l’applicazione vera:
Proxmox Host (Intel N97)
├── LXC 100: Uptime Kuma (Docker)
├── LXC 104: Immich (Docker)
├── LXC 105: Jellyfin (Docker)
├── LXC 106: Arr Stack (Docker)
├── LXC 107: Frigate (Docker)
├── LXC 109: Zigbee2MQTT (Docker)
├── LXC 110: Speedtest Tracker (Docker)
├── VM 101: Home Assistant OS
└── Tailscale LXC (no Docker)
Ogni LXC con Docker ha nesting=1 abilitato. I più interessanti – Immich, Jellyfin, Arr, Frigate, Zigbee2MQTT – girano come container privilegiati perché hanno bisogno di bind mount verso storage esterno o passthrough di dispositivi.
Il tutto è gestito da Ansible. I playbook creano gli LXC, installano Docker, generano i file docker-compose.yml a partire da template e avviano gli stack. Ripetibile. Consistente. Persino elegante.
Qual è il problema, allora?
Gli Argomenti Contro
I critici non hanno torto. Il loro argomento centrale ha mordente:
Stai annidando container dentro container. LXC è un runtime per container. Docker è un runtime per container. Far girare Docker dentro LXC significa pagare due livelli di isolamento dei namespace, due livelli di gestione dei cgroup, due livelli di astrazione del filesystem. Su un N97 con 16GB, quell’overhead non è gratis.
LXC privilegiato vanifica lo scopo. Il principale argomento di sicurezza per LXC è l’esecuzione non privilegiata: il mapping degli user namespace impedisce che il root del container coincida con il root dell’host. Ma sei dei miei container girano privilegiati perché hanno bisogno di passthrough USB (Zigbee2MQTT), accesso alla GPU (Jellyfin), o bind mount verso storage esterno (Immich, Arr). A quel punto, l’“isolamento” è un accordo tra gentiluomini, non un confine di sicurezza reale.
Lo storage a tre livelli è un casino in attesa di esplodere. Considera il percorso di una foto verso Immich:
USB Drive → bind mount Proxmox → mount point LXC → volume Docker → Immich
Quattro salti. Quando qualcosa si rompe, devi fare debug attraverso tre livelli di namespace di mount. Ci sono già passato. Non è divertente.
L’alternativa è semplice. Far girare Docker direttamente su Proxmox, o in una singola VM. Un livello. Niente annidamento. L’ecosistema Docker Compose funziona comunque. Perdi solo gli snapshot Proxmox per singolo servizio.
Gli Argomenti A Favore
Però ecco il punto. I critici ragionano dalla teoria. Io ragiono da un anno di questa cosa in produzione.
La granularità del backup per servizio è reale. Quando aggiorno Frigate e si rompe, ripristino lo snapshot di quell’LXC. Immich continua a servire foto. Jellyfin continua a fare streaming. Il blast radius è un container. Con una singola VM Docker, un docker-compose pull sbagliato sullo stack giusto e un momento di distrazione potrebbe avere effetti a cascata.
I limiti di risorse sono imposti da Proxmox, non da Docker. I constraint di Docker sono suggerimenti. Quelli di Proxmox sono reali. Quando il rilevamento ML di Frigate fa piccare la CPU, urta contro il muro che ho impostato nella config LXC. Il modello ML di Immich non va in starvation. Su un N97 vincolato, questo conta.
L’ecosistema Docker non è negoziabile. Immich porta con sé 6 container (server, ML, Redis, Postgres, microservices, proxy). Frigate integra il TPU Coral, un client MQTT e Go2RTC. Lo stack Arr è composto da 5 container. Questi progetti sono progettati per Docker Compose. Installarli “nativamente” in LXC significherebbe mantenere pacchetti custom, gestire conflitti di dipendenze e procedure di aggiornamento manuali. Passerei più tempo a fare il package maintainer che a gestire un homelab.
L’overhead è misurabile, ed è piccolo. Ogni LXC aggiunge circa 30-50MB di RAM base. Questo è il costo del livello extra di namespace. Con 16GB, far girare 10 LXC mi costa forse 400MB. Me lo posso permettere in cambio dei benefici operativi.
Il Verdetto Onesto
Sto seguendo un antipattern? Tecnicamente, sì. Annidare container è complessità aggiuntiva, e LXC privilegiato mina la storia dell’isolamento.
Lo cambierò? No.
Ecco perché. Le alternative scambiano tutte un problema con un altro:
| Approccio | Cosa guadagni | Cosa perdi |
|---|---|---|
| Docker diretto su Proxmox | Semplicità, niente annidamento | Tutto l’isolamento, snapshot per servizio impossibili |
| Singola VM Docker | Layer singolo pulito | Backup per servizio, controllo granulare delle risorse |
| VM multiple | Isolamento reale | 2-4GB RAM per VM, l’N97 non regge |
| LXC nativo (no Docker) | Il più leggero | L’intero ecosistema Docker, i tuoi weekend |
La risposta “giusta” dipende dall’hardware e dalle priorità. Su un server rack Xeon con 64GB, probabilmente farei girare una singola VM Docker e la chiuderei lì. Ma su un N97 con 16GB dove la RAM è preziosa e la semplicità operativa conta, l’approccio Docker-in-LXC trova un punto di equilibrio: più leggero delle VM, più isolato di Docker nudo, compatibile con l’intero ecosistema self-hosted.
Cosa Farei Diversamente
Se ricominciassi da zero, due cambiamenti:
Meno container privilegiati. Alcuni dei miei container sono privilegiati per comodità, non per necessità. Dedicerei il tempo per far funzionare la modalità non privilegiata con le giuste entry
lxc.idmapelxc.cgroup2.devices.allow. Ogni container privilegiato che elimini è un miglioramento reale alla sicurezza.Raggruppare i servizi correlati. Lo stack Arr (Radarr, Sonarr, Prowlarr, qBittorrent, FlareSolverr) non ha bisogno di cinque LXC separati. Un LXC con uno stack Docker Compose va benissimo – condividono comunque il dominio di fallimento. Questo ridurrebbe il numero di LXC e libererebbe risorse.
Il Punto
Il pattern Docker-in-LXC non è pulito. I puristi ti diranno che è sbagliato. Ma la purezza non fa girare Immich, non fa il backup delle foto di tua figlia, e non tiene Frigate a guardare la porta d’ingresso.
Il vero antipattern non è annidare container. È passare così tanto tempo a ottimizzare l’architettura da non usarla mai davvero.
Fai girare il workload. Monitora le risorse. Risolvi quello che si rompe davvero.
E controlla i backup.
Stato missione: tenuta. L’architettura è non ortodossa, i tradeoff sono compresi, e i backup girano ogni notte. A volte basta questo.
