Perché due container sullo stesso host non si vedono di default

Avvia Postgres e la tua API con un semplice docker run e l’API non riesce a raggiungere il database, anche se entrambi i container girano sulla stessa macchina.

1
2
docker run -d --name db postgres:16
docker run -d --name api myapp

Entrambi finiscono sulla bridge di default di Docker, quella su cui va a finire ogni container Docker se non specifichi altro. Ricevono un indirizzo IP. Quello che non ricevono è un modo per trovarsi a vicenda per nome: l’API dovrebbe quindi avere l’IP interno del database scritto a mano, un indirizzo che cambia ogni volta che il container riparte.

Una rete Docker risolve il problema. È uno switch virtuale a cui i container si collegano, con un proprio intervallo di IP e un proprio DNS. Creandone una, i nomi dei container diventano hostname e decidi tu chi può vedere chi.

Quale driver di rete usare: bridge, host o overlay

Docker include diversi driver di rete predefiniti. Tre di questi coprono quasi tutto ciò che incontrerai su un singolo host o su uno swarm:

DriverAmbitoI container hanno un IP proprioDNS per nomeUso tipico
bridge (default)singolo hostno (solo IP)quello che ottieni senza il flag --network
bridge (user-defined)singolo hostla scelta di default per sviluppo locale e deployment su singolo host
hostsingolo hostno, condivide lo stack di rete dell’hostn/aservizi ad alto throughput, strumenti che devono vedere la rete reale dell’host
overlaypiù host (swarm)servizi distribuiti su più host Docker

C’è anche none, che dà al container solo un’interfaccia di loopback e nient’altro. Utile per un job batch che non deve avere alcun accesso di rete.

Ogni docker run finisce sulla bridge di default. Funziona, ma instrada solo per IP: il vecchio flag --link serviva a coprire l’assenza dei nomi, e Docker lo considera legacy. Una bridge user-defined fa lo stesso lavoro con il DNS già incluso. Ed è quella che vuoi.

Come creare una rete Docker e collegarci i container

1
docker network create app-net

Questa è una bridge privata con una propria subnet, isolata dalla bridge di default e da ogni altra rete user-defined che crei. Collega i container all’avvio:

1
2
docker run -d --name db --network app-net -e POSTGRES_PASSWORD=devpass postgres:16
docker run -d --name api --network app-net -p 8080:8080 myapp

Dentro api, connettersi a db:5432 funziona. Nessuna ricerca di IP, nessun giro di variabili d’ambiente: il DNS integrato di Docker risolve il nome del container db con il suo indirizzo attuale su app-net, e aggiorna la mappatura quando il container riparte con un IP nuovo. Docker Compose si appoggia allo stesso meccanismo. docker compose up crea una rete per lo stack, e ogni servizio raggiunge gli altri per nome del servizio.

Puoi anche collegare un container già in esecuzione, senza riavviarlo:

1
2
docker network connect app-net some-container
docker network disconnect app-net some-container

Un container può stare su più reti contemporaneamente. È così che dai a un servizio una rete esposta pubblicamente e una privata condivisa solo con i suoi container backend.

Perché pubblicare una porta non è il modo in cui due container comunicano

-p 8080:8080 mappa la porta 8080 dell’host su quella del container, così puoi raggiungere l’API dal browser su localhost:8080. Non dice nulla sul fatto che api possa raggiungere db. Il traffico tra container sulla stessa rete user-defined resta interno, porte pubblicate o no.

Pubblica solo le porte che qualcosa fuori dall’host Docker deve raggiungere. Un database che parla solo con l’API non ha bisogno di -p 5432:5432: lascialo non pubblicato e né l’host né internet potranno connettersi direttamente. api continua comunque a raggiungerlo, perché condividono app-net.

Quando usare –network host, e quando no

1
docker run --rm -d --network host --name my_nginx nginx

Con --network host il container salta il proprio namespace di rete e si lega direttamente alle interfacce dell’host. Niente NAT, niente bridge, niente mappatura di porte: -p e -P vengono ignorati, e Docker stampa un warning se li passi comunque. Questo elimina un piccolo overhead per pacchetto e lascia al container la vista reale delle interfacce di rete dell’host: utile per gli strumenti di monitoraggio di rete e per i software che hanno bisogno di un range di porte ampio e contiguo.

Toglie anche l’isolamento che dà una bridge. Il container può legarsi a qualunque porta libera dell’host, e vede tutto ciò che c’è sulla rete dell’host. Su Linux funziona senza limitazioni. Docker Desktop per Mac e Windows richiede di attivarlo esplicitamente (disponibile da Docker Desktop 4.34), perché la rete della VM di Docker non è quella dell’host. I container Windows non lo supportano affatto. Per la maggior parte dei servizi, una bridge user-defined con porte pubblicate fa lo stesso lavoro con meno esposizione.

Quando serve una rete overlay al posto di una bridge

Le reti bridge e host si fermano al confine di un singolo host Docker. Esegui Docker Swarm su più macchine e un servizio sull’host A ha bisogno di una rete overlay per raggiungere un servizio sull’host B:

1
docker network create -d overlay --attachable app-overlay

Un’overlay incapsula il traffico dei container e lo instrada tra i daemon Docker di ogni nodo dello swarm: i nomi dei container su macchine fisiche o virtuali diverse si risolvono esattamente come su una bridge. Il flag --attachable permette anche ai container standalone di collegarcisi; senza, possono farlo solo i servizi swarm. Se non usi swarm o un altro orchestratore multi-host, salta questo driver. Una rete bridge su singolo host copre gli stack Compose e i setup locali.

Come ispezionare e risolvere i problemi di una rete Docker

1
docker network ls

Ogni rete presente sull’host, incluse bridge, host e none che Docker crea sempre.

1
docker network inspect app-net

La subnet, il gateway e ogni container collegato in quel momento con il suo IP su quella rete. È il primo posto dove guardare quando un container non riesce a raggiungerne un altro. Leggi la sezione Containers: se uno dei due manca, non è su questa rete, e docker network connect lo mette lì.

Per verificare la risoluzione DNS dall’interno di un container in esecuzione:

1
docker exec api getent hosts db

Un IP di ritorno significa che la risoluzione dei nomi funziona e il problema è nell’applicazione: porta sbagliata, o il servizio non è ancora in ascolto. Nessuna risposta significa che i due container non sono sulla stessa rete. Conferma con docker network inspect, oppure chiedi a ciascun container a cosa è collegato:

1
docker inspect <container> --format '{{ .NetworkSettings.Networks }}'

Una rete per ogni stack, con tutti i container sopra

La bridge di default è quella che hai gratis. Non è quella su cui dovresti costruire. Esegui docker network create <app>-net per ogni stack applicativo, collegaci tutti i container di cui quello stack ha bisogno, e riserva --network host ai rari servizi che hanno davvero bisogno dello stack di rete dell’host.

Quando gestisci a mano più di un paio di container, smetti di farlo a mano. Docker Compose crea la rete e ci collega i container al posto tuo da un unico file YAML. I volumi Docker coprono l’altra metà del problema di sopravvivere a un riavvio: i dati che quei container scrivono.

Articoli correlati