Fa quatre lliçons que arrossegues la mateixa avaria i ja saps exactament què la causa: nslookup aurora-db retorna NXDOMAIN perquè la xarxa bridge per defecte no té DNS intern entre contenidors. La connectivitat hi és —nc -zv 172.17.0.2 5432 respon succeeded—, però l'API no sap com es diu la seva base de dades.

Aquesta lliçó és la que ho arregla, i amb dues comandes. Però abans d'escriure-les entendràs de debò el model de xarxa de Docker: per què cada contenidor té el seu propi localhost, què fa cadascun dels drivers integrats, en què es diferencia la bridge per defecte d'una xarxa creada per tu, i quina és la diferència —que es confon constantment— entre publicar un port i que dos contenidors es parlin. Després crearàs aurora-net, mouràs els tres serveis a dins, i executaràs el curl que fa tot el curs que esperes. I per rematar-ho, posaràs davant aurora-web amb Nginx com a proxy invers, i obriràs la llibreria completa al navegador.

Contingut

  1. Per què els contenidors no es veuen entre ells
  2. Els drivers de xarxa integrats
  3. La bridge per defecte enfront de les xarxes definides per l'usuari
  4. Les comandes de docker network
  5. Publicació de ports, revisitada
  6. Pràctica: aurora-net i la resolució de noms
  7. El moment de la veritat: /llibres retorna els vuit llibres
  8. aurora-web: Nginx com a proxy invers
  9. Connectar i desconnectar xarxes en calent
  10. Bones pràctiques de xarxa

  1. Per què els contenidors no es veuen entre ells

Un contenidor no és només un procés aïllat: té el seu propi namespace de xarxa. Això significa que posseeix, en exclusiva, la seva llista d'interfícies, la seva taula de rutes, les seves regles de tallafocs, els seus ports i —això és el que confon tothom— el seu propi localhost.

flowchart TB
    subgraph HOST["Host (la teva màquina)"]
        direction TB
        HL["localhost del host<br/>127.0.0.1"]
        subgraph C1["Contenidor aurora-api"]
            L1["EL SEU localhost<br/>127.0.0.1"]
            E1["eth0: 172.17.0.4"]
            P1["node escoltant a :3000"]
        end
        subgraph C2["Contenidor aurora-db"]
            L2["EL SEU localhost<br/>127.0.0.1"]
            E2["eth0: 172.17.0.2"]
            P2["postgres escoltant a :5432"]
        end
        BR["docker0<br/>bridge 172.17.0.1"]
        E1 --- BR
        E2 --- BR
    end
    L1 -. "NO és el mateix" .- L2
    L1 -. "NO és el mateix" .- HL

Tres localhost diferents a la mateixa màquina. Quan server.js intentava connectar-se a localhost:5432 al mòdul 2, estava preguntant per PostgreSQL dins del seu propi contenidor, on només viu node. D'aquí l'ECONNREFUSED: algú va contestar (el mateix contenidor) dient que allà no hi ha ningú escoltant.

Comprova-ho tu mateix:

docker exec aurora-api hostname -i
docker exec aurora-db hostname -i
docker exec aurora-api sh -c 'nc -z localhost 5432; echo "PostgreSQL al meu localhost: $?"'
172.17.0.4
172.17.0.2
PostgreSQL al meu localhost: 1

Cadascun amb la seva IP, i al localhost de l'API no hi ha cap base de dades. La regla, en una línia:

Dins d'un contenidor, localhost sempre significa "jo mateix". Per parlar amb un altre contenidor cal fer servir la seva adreça: la seva IP o, molt millor, el seu nom.

  1. Els drivers de xarxa integrats

Docker implementa les xarxes mitjançant drivers intercanviables:

Driver Què fa Aïllament Quan fer-lo servir
bridge Xarxa virtual privada al host; cada contenidor rep una IP interna Alt Per defecte i per a gairebé tot: aplicacions de diversos contenidors en una màquina
host El contenidor comparteix la pila de xarxa del host: sense IP pròpia, sense NAT Cap Rendiment extrem, o serveis que necessiten veure tots els ports del host
none Només interfície de loopback: sense xarxa Total Treballs per lots que no necessiten xarxa; màxima seguretat
overlay Xarxa que abasta diversos hosts d'un clúster Alt Docker Swarm. Es veu a la lliçó 06-03
macvlan El contenidor rep una MAC i una IP de la teva xarxa física Mitjà Integrar contenidors en una LAN existent. Detall a la lliçó 05-01
ipvlan Similar, compartint la MAC del host Mitjà Entorns amb restriccions de MAC. Detall a la lliçó 05-01

Les tres primeres vénen preconfigurades i les veus amb:

docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
6b2f8a1c9e73   bridge    bridge    local
f19d4c7e2a85   host      host      local
3a8e1b5f9c24   none      null      local

Una ullada ràpida a host i none, que es fan servir poc però convé reconèixer:

docker run --rm --network host alpine:3.20 hostname -i
docker run --rm --network none alpine:3.20 sh -c 'ip -o addr show | awk "{print \$2, \$4}"'
192.168.1.42
lo 127.0.0.1/8

Amb --network host, el contenidor té la mateixa IP que la teva màquina: no hi ha -p que valgui perquè els ports són directament els del host, i no hi ha cap aïllament de xarxa. Amb --network none, només existeix lo: ni tan sols pot fer ping a res.

bridge host none
IP pròpia No, la del host No
Cal -p No, i no funciona Irrellevant
Aïllament de xarxa No Total
Rendiment Molt bo (hi ha NAT) El màxim
Conflictes de ports Només al host Directes entre contenidors No
Disponible a Docker Desktop (macOS/Windows) Amb limitacions

  1. La bridge per defecte enfront de les xarxes definides per l'usuari

Aquí hi ha el cor de la lliçó. Existeixen dos tipus de xarxa bridge i es comporten de manera molt diferent:

Bridge per defecte (bridge, docker0) Bridge definida per l'usuari
Com es fa servir Automàtica: tot contenidor sense --network hi cau docker network create la-meva-xarxa + --network la-meva-xarxa
DNS intern entre contenidors NO : cada contenidor es resol pel seu nom
Aïllament Tots els contenidors junts, inclosos els d'altres projectes Només els que connectis a aquesta xarxa
Connectar/desconnectar en calent No , amb network connect/disconnect
Àlies de xarxa No Sí, amb --network-alias
Variables d'entorn d'enllaç Sistema --link, obsolet No cal
Recomanació Evitar-la Fer-la servir sempre

Només la primera fila importa avui, però és determinant: en una xarxa pròpia, Docker aixeca un servidor DNS intern a 127.0.0.11 que resol els noms dels contenidors connectats a aquesta xarxa. A la bridge per defecte, aquest DNS existeix però només reenvia consultes als servidors del host: no coneix cap nom de contenidor. Aquest és, literalment, tot el misteri que arrossegues des de la lliçó 02-06.

Abans d'arreglar-ho, deixa constància del punt de partida:

docker exec aurora-api getent hosts aurora-db; echo "resultat: $?"
resultat: 2

  1. Les comandes de docker network

Comanda Per a què
docker network ls Llistar xarxes
docker network create Crear una xarxa
docker network inspect Veure'n la configuració i els contenidors
docker network connect Connectar un contenidor en marxa a una xarxa
docker network disconnect Desconnectar-lo
docker network rm Esborrar una xarxa
docker network prune Esborrar totes les xarxes sense contenidors

Crear una xarxa

docker network create aurora-net
c7f2e9a13b8d46052fa8c1e7b3d9a4f6c2e0b8d5a3f1c9e7b5d3a1f9c7e5b3d1

Amb opcions, quan necessitis control:

docker network create \
  --driver bridge \
  --subnet 172.28.0.0/16 \
  --gateway 172.28.0.1 \
  --label projecte=aurora-libros \
  aurora-net-personalitzada
Opció Per a què
--driver El driver; bridge és el valor per defecte
--subnet Fixar el rang d'IPs (útil si xoca amb la teva VPN)
--gateway La passarel·la d'enllaç d'aquesta subxarxa
--ip-range Restringir el rang d'assignació automàtica
--internal Xarxa sense sortida a Internet: aïllament total cap a fora
--label Etiquetes, com als contenidors
--attachable Permetre connectar contenidors solts a una xarxa overlay

--internal mereix atenció: una xarxa interna permet que els contenidors es parlin entre ells però els talla la sortida a l'exterior. És una defensa excel·lent per a una base de dades que no té per què sortir a Internet, i es reprèn a la lliçó 05-03.

Inspeccionar

docker network inspect aurora-net --format '{{.Name}} | driver={{.Driver}} | subxarxa={{range .IPAM.Config}}{{.Subnet}}{{end}}'
aurora-net | driver=bridge | subxarxa=172.18.0.0/16

I la consulta més útil de totes: qui hi està connectat.

docker network inspect aurora-net --format '{{range .Containers}}{{.Name}} → {{.IPv4Address}}{{println}}{{end}}'

De moment no imprimeix res: la xarxa és buida.

Esborrar i netejar

docker network rm aurora-net-personalitzada
docker network prune -f
aurora-net-personalitzada

Una xarxa amb contenidors connectats no es pot esborrar (network ... has active endpoints). I les tres xarxes predefinides —bridge, host, none— no es poden eliminar mai.

  1. Publicació de ports, revisitada

Ara que entens el model, la taula de la lliçó 03-01 cobra sentit ple:

Sintaxi Efecte
-p 3000:3000 Port 3000 de totes les interfícies del host → 3000 del contenidor
-p 127.0.0.1:5432:5432 Només accessible des del mateix host
-p 8080-8090:8080-8090 Un rang de ports
-p 5514:514/udp Publicació UDP
-p 3000 Port aleatori del host → 3000 del contenidor
-P Publica tots els EXPOSE en ports aleatoris
(res) El contenidor no és accessible des del host, però sí des de la seva xarxa

I aquesta és la distinció que cal tenir absolutament clara:

Publicar un port (-p) Comunicació entre contenidors
Per a què serveix Que el host i el món exterior arribin al contenidor Que un contenidor arribi a un altre
Com s'adreça localhost:PORT_HOST des del host nom-contenidor:PORT_INTERN
Requereix -p? Sí, és la seva definició No, en absolut
Port que es fa servir El del host (el de l'esquerra) L'intern del contenidor, sempre
Requisit Cap llevat que el port estigui lliure Ser a la mateixa xarxa definida per l'usuari

D'aquí surten dos errors clàssics que ara pots evitar:

  • Publicar ports "perquè els contenidors es vegin". No cal i a sobre exposa serveis interns. aurora-db no necessita cap -p perquè l'API la faci servir; només el necessites tu, si vols connectar-t'hi amb un client des del host.
  • Fer servir el port del host a la cadena de connexió d'un altre contenidor. Si publiques -p 5433:5432, un altre contenidor s'ha de continuar connectant a aurora-db:5432, el port intern. El 5433 només existeix per al host.

  1. Pràctica: aurora-net i la resolució de noms

Mans a l'obra. Primer, la xarxa:

docker network create --label projecte=aurora-libros aurora-net
docker network ls --filter name=aurora-net
c7f2e9a13b8d46052fa8c1e7b3d9a4f6c2e0b8d5a3f1c9e7b5d3a1f9c7e5b3d1
NETWORK ID     NAME         DRIVER    SCOPE
c7f2e9a13b8d   aurora-net   bridge    local

Ara recrea els tres serveis a dins seu. Com que la xarxa és un ajust de namespace, es fixa en crear el contenidor (lliçó 03-01), així que cal esborrar i tornar a crear:

docker rm -f aurora-db aurora-cache aurora-api
docker run -d \
  --name aurora-db \
  --network aurora-net \
  --label projecte=aurora-libros --label component=base-de-dades \
  -e POSTGRES_USER=aurora \
  -e POSTGRES_PASSWORD=aurora_secreta \
  -e POSTGRES_DB=aurora_llibres \
  -p 127.0.0.1:5432:5432 \
  postgres:16-alpine
docker run -d \
  --name aurora-cache \
  --network aurora-net \
  --label projecte=aurora-libros --label component=cache \
  redis:7-alpine

Fixa't en un canvi important a aurora-cache: ja no porta -p. Ningú des del host no necessita parlar amb Redis; només l'API, i per a això n'hi ha prou de ser a la mateixa xarxa. És el principi d'exposar el mínim imprescindible.

Abans d'arrencar l'API, torna a carregar el catàleg, perquè el contenidor de la base de dades és nou:

sleep 5
docker cp ~/aurora-libros/db/init.sql aurora-db:/tmp/init.sql
docker exec aurora-db psql -U aurora -d aurora_llibres -f /tmp/init.sql
docker exec aurora-db psql -U aurora -d aurora_llibres -t -c "SELECT COUNT(*) FROM llibres;"
Successfully copied 3.07kB to aurora-db:/tmp/init.sql
CREATE TABLE
CREATE INDEX
INSERT 0 8
     8

Apunta't aquesta molèstia, perquè és la segona vegada que la pateixes: cada vegada que recrees aurora-db perds les dades i les has de tornar a carregar a mà. Aquesta és la mancança que resol la lliçó 03-06.

I ara l'API:

docker run -d \
  --name aurora-api \
  --network aurora-net \
  --label projecte=aurora-libros --label component=api \
  --env-file ~/aurora-libros/aurora.env \
  -p 3000:3000 \
  auroralibros/aurora-api:1.2.0

Verifica la resolució de noms

docker exec aurora-api getent hosts aurora-db
docker exec aurora-api getent hosts aurora-cache
docker exec aurora-api getent hosts aurora-api
172.18.0.2       aurora-db
172.18.0.3       aurora-cache
172.18.0.4       aurora-api

Ja hi som. La mateixa comanda que fa deu minuts retornava codi 2 i cap línia, ara resol els tres noms. No has canviat ni una línia de server.js, ni el Dockerfile, ni el fitxer d'entorn: només has posat els contenidors en una xarxa pròpia.

Comprova també la connectivitat real, port a port:

docker run --rm --network aurora-net nicolaka/netshoot \
  sh -c 'nc -zv aurora-db 5432; nc -zv aurora-cache 6379; nc -zv aurora-api 3000'
Connection to aurora-db (172.18.0.2) 5432 port [tcp/postgresql] succeeded!
Connection to aurora-cache (172.18.0.3) 6379 port [tcp/redis] succeeded!
Connection to aurora-api (172.18.0.4) 3000 port [tcp/*] succeeded!

Tots tres, per nom. I fixa't en el detall: aurora-cache respon al 6379 encara que no vas publicar cap port. La publicació és per al host; dins de la xarxa, tots els ports del contenidor estan disponibles per als seus veïns.

I l'estat de la flota:

docker ps --filter label=projecte=aurora-libros --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
NAMES          STATUS                   PORTS
aurora-api     Up 30 seconds (healthy)  0.0.0.0:3000->3000/tcp
aurora-cache   Up 1 minute              6379/tcp
aurora-db      Up 2 minutes             127.0.0.1:5432->5432/tcp

(healthy). Aquest parèntesi fa quatre lliçons que diu unhealthy. El HEALTHCHECK que vas escriure a la lliçó 02-04 fa la seva comprovació contra /salut, /salut consulta PostgreSQL i Redis, tots dos responen, i per primera vegada retorna 200. I fixa't en la línia d'aurora-cache: 6379/tcp sense més, sense fletxa. Exposat però no publicat.

  1. El moment de la veritat: /llibres retorna els vuit llibres

curl -s http://localhost:3000/salut | jq
{
  "servei": "aurora-api",
  "version": "1.0.0",
  "db": "ok",
  "cache": "ok"
}

db: ok. cache: ok. I ara, la comanda que fa tot el curs que esperes:

curl -s http://localhost:3000/llibres | jq
{
  "origen": "db",
  "llibres": [
    { "id": 3, "titol": "Cien años de soledad", "autor": "Gabriel García Márquez", "isbn": "978-84-397-2071-7", "preu": "17.95" },
    { "id": 1, "titol": "El jardín de senderos que se bifurcan", "autor": "Jorge Luis Borges", "isbn": "978-84-206-3312-1", "preu": "14.50" },
    { "id": 8, "titol": "El tiempo entre costuras", "autor": "María Dueñas", "isbn": "978-84-8365-351-1", "preu": "20.15" },
    { "id": 6, "titol": "La casa de los espíritus", "autor": "Isabel Allende", "isbn": "978-84-9838-618-3", "preu": "18.40" },
    { "id": 4, "titol": "La sombra del viento", "autor": "Carlos Ruiz Zafón", "isbn": "978-84-08-04364-5", "preu": "21.00" },
    { "id": 7, "titol": "Los detectives salvajes", "autor": "Roberto Bolaño", "isbn": "978-84-339-6835-7", "preu": "23.60" },
    { "id": 5, "titol": "Nada", "autor": "Carmen Laforet", "isbn": "978-84-233-4361-2", "preu": "12.75" },
    { "id": 2, "titol": "Rayuela", "autor": "Julio Cortázar", "isbn": "978-84-376-0494-7", "preu": "19.90" }
  ]
}

Els vuit llibres. El jardín de senderos que se bifurcan, Rayuela, Cien años de soledad, La sombra del viento, Nada, La casa de los espíritus, Los detectives salvajes i El tiempo entre costuras, ordenats per títol, servits per una API en un contenidor, llegits d'una base de dades PostgreSQL en un altre contenidor, a través d'una xarxa virtual que has creat tu. Atura't un segon en això: la plataforma d'Aurora Libros està viva. És la primera vegada des de la lliçó 01-07, on l'executaves a mà en quinze passos i fallava, que el sistema complet funciona d'extrem a extrem.

I hi ha una segona comprovació, més subtil, que demostra que la memòria cau també està fent la seva feina:

curl -s http://localhost:3000/llibres | jq -r '.origen'
curl -s http://localhost:3000/llibres | jq -r '.origen'
docker exec aurora-cache redis-cli KEYS '*'
docker exec aurora-cache redis-cli TTL llibres:tots
db
cache
1) "llibres:tots"
(integer) 47

La primera crida va anar a PostgreSQL i va desar el resultat a Redis amb un TTL de 60 segons; la segona ja el va servir des de la memòria cau. Els tres contenidors estan cooperant: l'API parla amb la base de dades pel seu nom i amb la memòria cau pel seu. Comprova-ho també per l'altre extrem:

curl -s http://localhost:3000/llibres/2 | jq
{
  "id": 2,
  "titol": "Rayuela",
  "autor": "Julio Cortázar",
  "isbn": "978-84-376-0494-7",
  "preu": "19.90"
}

Què ha canviat exactament

Abans (bridge per defecte) Ara (aurora-net)
getent hosts aurora-db Res, codi 2 172.18.0.2 aurora-db
/salut 503, db: ko, cache: ko 200, db: ok, cache: ok
Estat del contenidor Up (unhealthy) Up (healthy)
/llibres {"error": "No s'ha pogut obtenir el catàleg"} Els vuit llibres
Canvis al codi Cap

Aquesta última fila és la lliçó que cal endur-se: el problema mai no va ser a l'aplicació ni a la imatge. Era a com es connectaven els contenidors entre ells.

  1. aurora-web: Nginx com a proxy invers

Falta el quart servei. El teu web/index.html de la lliçó 01-07 crida /api/llibres, una ruta relativa, no http://localhost:3000/llibres. Aquell disseny era deliberat: el navegador ha de parlar amb un únic origen, i qui reparteix per dins és Nginx.

Crea ~/aurora-libros/web/nginx.conf:

server {
    listen 80;
    server_name _;

    # Docker resol els noms de contenidor a 127.0.0.11.
    # Amb "valid=10s" Nginx reconsulta el DNS i no es queda amb una IP vella
    # si aurora-api es recrea i canvia d'adreça.
    resolver 127.0.0.11 valid=10s;

    # 1) La web estàtica
    location / {
        root  /usr/share/nginx/html;
        index index.html;
    }

    # 2) Tot el que comenci per /api/ es reenvia a aurora-api
    location /api/ {
        set $api_upstream http://aurora-api:3000;
        proxy_pass $api_upstream/;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout 5s;
        proxy_read_timeout   30s;
    }
}

Línia a línia, el que importa:

Directiva Què fa
listen 80 Nginx escolta al 80 dins del contenidor
resolver 127.0.0.11 valid=10s Fa servir el DNS intern de Docker i refresca la resolució cada 10 s
set $api_upstream ... + proxy_pass $api_upstream/ En fer servir una variable, Nginx resol el nom a cada petició en comptes de només en arrencar. Sense aquest truc, si recrees aurora-api i li canvia la IP, el proxy continua apuntant a la vella i retorna 502
La / final de proxy_pass ...$api_upstream/ Retalla el prefix: /api/llibres es reenvia com a /llibres, que és la ruta que exposa l'API
X-Forwarded-For i companyia Capçaleres estàndard perquè l'API sàpiga qui és el client original
proxy_connect_timeout 5s No deixar peticions penjades eternament si l'API no respon

Arrenca el contenidor:

docker run -d \
  --name aurora-web \
  --network aurora-net \
  --label projecte=aurora-libros --label component=web \
  -p 8080:80 \
  -v ~/aurora-libros/web/index.html:/usr/share/nginx/html/index.html:ro \
  -v ~/aurora-libros/web/nginx.conf:/etc/nginx/conf.d/default.conf:ro \
  --stop-signal SIGQUIT \
  nginx:alpine

Dos detalls de la comanda:

  • Els dos -v munten fitxers del host en només lectura (:ro). És la sintaxi mínima de la lliçó 03-01; la lliçó 03-06 explica què són exactament aquests muntatges i per què :ro és important en fitxers de configuració.
  • --stop-signal SIGQUIT aplica el que vas aprendre a la lliçó 02-04: Nginx fa una aturada ordenada amb SIGQUIT i una d'abrupta amb SIGTERM. Amb aquesta opció, docker stop aurora-web no talla peticions a mig servir.

Comprova la cadena completa:

curl -s -o /dev/null -w "web estàtica: HTTP %{http_code}\n" http://localhost:8080/
curl -s http://localhost:8080/api/llibres | jq -r '.llibres[] | .titol' | head -4
curl -s http://localhost:8080/api/salut | jq -c
web estàtica: HTTP 200
Cien años de soledad
El jardín de senderos que se bifurcan
El tiempo entre costuras
La casa de los espíritus
{"servei":"aurora-api","version":"1.0.0","db":"ok","cache":"ok"}

Obre http://localhost:8080 al navegador. La pàgina d'Aurora Libros carrega, crida /api/llibres, Nginx la reenvia a aurora-api, l'API consulta PostgreSQL o Redis, i la taula s'omple amb els vuit títols i els seus preus en euros. L'arquitectura completa, funcionant:

flowchart LR
    N["Navegador<br/>localhost:8080"] --> W
    subgraph NET["Xarxa aurora-net (172.18.0.0/16)"]
        W["aurora-web<br/>nginx:alpine<br/>:80"]
        A["aurora-api<br/>node 22<br/>:3000"]
        D["aurora-db<br/>postgres 16<br/>:5432"]
        C["aurora-cache<br/>redis 7<br/>:6379"]
        W -- "/api/ → http://aurora-api:3000/" --> A
        A -- "aurora-db:5432" --> D
        A -- "aurora-cache:6379" --> C
    end

Ni una sola IP escrita a mà enlloc: aurora-web busca aurora-api, i aurora-api busca aurora-db i aurora-cache, tots per nom.

  1. Connectar i desconnectar xarxes en calent

Amb xarxes definides per l'usuari, i només amb elles, pots canviar la topologia sense recrear res:

docker network create aurora-admin
docker network connect aurora-admin aurora-db
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}={{$v.IPAddress}} {{end}}' aurora-db
aurora-admin=172.19.0.2 aurora-net=172.18.0.2

aurora-db té ara dues interfícies i dues IPs, una a cada xarxa. És el patró habitual per donar accés a una eina d'administració sense obrir la xarxa de l'aplicació:

docker run -d --name aurora-adminer --network aurora-admin -p 8081:8080 adminer:latest
docker exec aurora-adminer getent hosts aurora-db
172.19.0.2       aurora-db

Adminer veu la base de dades, però no veu aurora-api ni aurora-cache, perquè no és a aurora-net. Aïllament per disseny.

docker network disconnect aurora-admin aurora-db
docker rm -f aurora-adminer
docker network rm aurora-admin

I un apunt sobre àlies de xarxa, que seran importants al mòdul 4:

docker run -d --name aurora-db-nou --network aurora-net \
  --network-alias base-de-dades --network-alias postgres \
  -e POSTGRES_PASSWORD=aurora_secreta postgres:16-alpine
docker exec aurora-api getent hosts base-de-dades
docker rm -f aurora-db-nou
172.18.0.6       base-de-dades

Un contenidor pot respondre a diversos noms a la mateixa xarxa. És el que permet, per exemple, canviar d'implementació sense tocar la configuració de qui la consumeix.

  1. Bones pràctiques de xarxa

Pràctica Per què
Una xarxa per aplicació aurora-net conté només els quatre serveis d'Aurora Libros. Un altre projecte, una altra xarxa. Sense contactes accidentals
No facis servir mai la bridge per defecte Sense DNS, sense aïllament i amb tots els contenidors de la màquina junts
Publica només el que necessiti l'exterior aurora-web al 8080 i prou. aurora-cache no porta -p, i aurora-db només a 127.0.0.1
Lliga les bases de dades a 127.0.0.1 -p 127.0.0.1:5432:5432 et deixa fer servir un client local sense exposar PostgreSQL a la wifi
Refereix-te als serveis pel nom, mai per IP Les IPs canvien a cada arrencada; els noms no
Fes servir el port intern a les cadenes de connexió Si publiques -p 5433:5432, un altre contenidor continua fent servir aurora-db:5432
Considera --internal per a les xarxes de dades Talla la sortida a Internet de serveis que no la necessiten
Etiqueta les xarxes --label projecte=aurora-libros permet netejar sense destrossar el que és d'altri

Com queda la superfície exposada d'Aurora Libros:

docker ps --filter label=projecte=aurora-libros --format "table {{.Names}}\t{{.Ports}}"
NAMES          PORTS
aurora-web     0.0.0.0:8080->80/tcp
aurora-api     0.0.0.0:3000->3000/tcp
aurora-cache   6379/tcp
aurora-db      127.0.0.1:5432->5432/tcp

En un desplegament real, fins i tot el -p 3000:3000 de l'API sobraria: si el navegador només parla amb Nginx, l'API no necessita estar publicada. Ho pots provar recreant-la sense -p i veuràs que http://localhost:8080/api/llibres continua funcionant perfectament, perquè el proxy hi arriba per la xarxa interna.

El que aquesta lliçó no cobreix, i on es veu: com funciona el bridge per dins (parelles veth, iptables, nat), les xarxes overlay entre diversos hosts, macvlan i les polítiques de xarxa s'estudien a la lliçó 05-01 i a la 06-03.

Errors Habituals i Consells

  • Fer servir localhost per referir-se a un altre contenidor. Dins d'un contenidor, localhost és ell mateix. Fes servir el nom de l'altre contenidor.
  • Deixar els contenidors a la bridge per defecte i esperar que es resolguin per nom. No passarà mai. Crea una xarxa amb docker network create.
  • Escriure IPs a mà. Funciona fins al següent reinici. Docker assigna les IPs per ordre d'arrencada.
  • Fer servir el port publicat del host a la connexió entre contenidors. Amb -p 5433:5432, un altre contenidor ha de continuar fent servir el 5432.
  • Publicar ports "perquè es vegin entre ells". No cal, i exposa serveis interns a tota la xarxa.
  • Intentar canviar de xarxa amb docker start. La xarxa es fixa en crear. Es canvia amb network connect/disconnect, o recreant.
  • 502 a Nginx després de recrear l'API. Nginx va desar a la memòria cau la IP antiga. Es resol amb resolver 127.0.0.11 valid=10s i proxy_pass sobre una variable, com a la configuració d'aquesta lliçó.
  • Oblidar la / final de proxy_pass. Sense ella, /api/llibres es reenvia com a /api/llibres i l'API respon 404. Amb ella, hi arriba com a /llibres.
  • Consell: docker network inspect és la manera més ràpida de respondre a "qui hi ha en aquesta xarxa i amb quina IP?".
  • Consell: per diagnosticar, llança netshoot connectat a la xarxa (--network aurora-net) i prova nc, dig i curl des de dins seu.

Exercicis

Exercici 1: demostra l'aïllament entre xarxes

Crea dues xarxes, xarxa-a i xarxa-b. Aixeca un contenidor servidor-a amb nginx:alpine a xarxa-a, i dos clients de nicolaka/netshoot: client-a a xarxa-a i client-b a xarxa-b. Demostra amb comandes que:

  1. client-a resol servidor-a pel nom i obté un HTTP 200.
  2. client-b no el resol ni hi arriba, ni per nom ni per IP.
  3. Després, sense recrear res, fes que client-b hi pugui arribar, i verifica quantes IPs té llavors.

Exercici 2: publicar enfront de comunicar

Aixeca una pila mínima en una xarxa xarxa-botiga: un mini-db amb redis:7-alpine sense cap -p, i un mini-api amb nicolaka/netshoot que executi sleep 3600, publicant -p 9999:80. Respon amb comandes i explicacions:

  1. Pot mini-api parlar amb mini-db? Demostra-ho.
  2. Pots tu, des del host, parlar amb mini-db amb redis-cli o nc? Per què?
  3. Què respon curl localhost:9999 i per què, si mini-api no té cap servidor web?
  4. Recrea mini-db amb -p 127.0.0.1:6380:6379 i repeteix el punt 2. Ha canviat alguna cosa per a mini-api?

Exercici 3: trenca i arregla el proxy invers

Amb la plataforma d'Aurora Libros funcionant, provoca i diagnostica dues avaries reals del proxy:

  1. Canvia a nginx.conf la destinació a http://aurora-api-inexistent:3000, recarrega Nginx amb docker kill -s SIGHUP aurora-web i observa què retorna curl localhost:8080/api/llibres. Diagnostica amb docker logs aurora-web.
  2. Restaura la destinació correcta però treu la barra final de proxy_pass. Recarrega i observa el nou error. Explica exactament quina ruta li està arribant a aurora-api (comprova-ho amb docker logs aurora-api).
  3. Deixa-ho tot correcte i demostra, recreant aurora-api sense -p, que la web continua funcionant a localhost:8080.

Solucions

Solució a l'exercici 1

docker network create xarxa-a
docker network create xarxa-b
docker run -d --name servidor-a --network xarxa-a nginx:alpine
docker run -d --name client-a --network xarxa-a nicolaka/netshoot sleep 3600
docker run -d --name client-b --network xarxa-b nicolaka/netshoot sleep 3600

1. Des de client-a:

docker exec client-a getent hosts servidor-a
docker exec client-a curl -s -o /dev/null -w "HTTP %{http_code}\n" http://servidor-a
172.20.0.2       servidor-a
HTTP 200

2. Des de client-b:

docker exec client-b nslookup servidor-a 2>&1 | tail -2
docker exec client-b sh -c 'nc -zv -w 3 172.20.0.2 80; echo "codi: $?"'
** server can't find servidor-a: NXDOMAIN
nc: connect to 172.20.0.2 port 80 (tcp) failed: Connection timed out
codi: 1

Aquí hi ha dues fallades diferents i totes dues importen: el nom no es resol (no comparteixen xarxa, així que el DNS de xarxa-b no coneix servidor-a) i tampoc no funciona la IP, perquè són dues bridges separades i el trànsit entre elles no està permès. Compara-ho amb el diagnòstic de la lliçó 03-04: allà el nom fallava però la IP funcionava, cosa que demostrava que sí que compartien xarxa. La combinació de les dues proves és el que distingeix "mateixa xarxa sense DNS" de "xarxes diferents".

3. Connectar en calent:

docker network connect xarxa-a client-b
docker exec client-b curl -s -o /dev/null -w "HTTP %{http_code}\n" http://servidor-a
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}={{$v.IPAddress}} {{end}}' client-b
HTTP 200
xarxa-a=172.20.0.4 xarxa-b=172.21.0.3

Dues IPs, una per xarxa, sense haver aturat ni recreat el contenidor. Això només és possible amb xarxes definides per l'usuari.

docker rm -f servidor-a client-a client-b
docker network rm xarxa-a xarxa-b

Solució a l'exercici 2

docker network create xarxa-botiga
docker run -d --name mini-db --network xarxa-botiga redis:7-alpine
docker run -d --name mini-api --network xarxa-botiga -p 9999:80 nicolaka/netshoot sleep 3600

1.

docker exec mini-api redis-cli -h mini-db PING
PONG

, perfectament, i mini-db no publica cap port. Dins d'una xarxa compartida, tots els ports del contenidor estan disponibles per als seus veïns: -p no hi té res a veure.

2.

nc -zv -w 3 localhost 6379; echo "codi: $?"
nc: connect to localhost port 6379 (tcp) failed: Connection refused
codi: 1

No. Com que mini-db no va publicar cap port, no existeix cap regla que reenviï trànsit del host cap a ell. El host i la xarxa de Docker són dos àmbits diferents: el host només arriba al que s'ha publicat explícitament.

3.

curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:9999
HTTP 000

La publicació existeixdocker ps mostra 0.0.0.0:9999->80/tcp—, però dins del contenidor no hi ha res escoltant al 80: mini-api només executa sleep. És la demostració que -p no comprova res: crea el reenviament encara que la destinació estigui buida. És el mateix error que es comet en invertir l'ordre a -p 80:8080.

4.

docker rm -f mini-db
docker run -d --name mini-db --network xarxa-botiga -p 127.0.0.1:6380:6379 redis:7-alpine
redis-cli -p 6380 PING 2>/dev/null || docker run --rm --network host redis:7-alpine redis-cli -p 6380 PING
docker exec mini-api redis-cli -h mini-db PING
PONG
PONG

Ara tu et pots connectar des del host pel port 6380, i per a mini-api no ha canviat absolutament res: continua fent servir mini-db:6379, el port intern. Els dos camins són independents.

docker rm -f mini-db mini-api
docker network rm xarxa-botiga

Solució a l'exercici 3

1. Destinació inexistent:

sed -i 's|http://aurora-api:3000|http://aurora-api-inexistent:3000|' ~/aurora-libros/web/nginx.conf
docker kill -s SIGHUP aurora-web
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8080/api/llibres
docker logs --tail 3 aurora-web
HTTP 502
2026/08/04 21:47:12 [error] 31#31: *5 aurora-api-inexistent could not be resolved (3: Host not found),
client: 172.18.0.1, server: _, request: "GET /api/llibres HTTP/1.1", host: "localhost:8080"

502 Bad Gateway és l'error de "el proxy no ha pogut parlar amb la destinació", i el registre en dona el motiu exacte: could not be resolved. És el mateix NXDOMAIN de la lliçó 03-04, vist des de Nginx. I val la pena notar que la web estàtica es continua servint sense problema a http://localhost:8080/: només falla la ruta /api/.

2. Sense la barra final:

sed -i 's|http://aurora-api-inexistent:3000|http://aurora-api:3000|' ~/aurora-libros/web/nginx.conf
sed -i 's|proxy_pass $api_upstream/;|proxy_pass $api_upstream;|' ~/aurora-libros/web/nginx.conf
docker kill -s SIGHUP aurora-web
curl -s -w "\nHTTP %{http_code}\n" http://localhost:8080/api/llibres
docker logs --tail 2 aurora-api
<!DOCTYPE html>...Cannot GET /api/llibres...
HTTP 404

Ara sí que arriba a l'API, però amb la ruta equivocada. Amb la barra final, proxy_pass http://aurora-api:3000/ substitueix el prefix /api/ per /, així que /api/llibres hi arriba com a /llibres. Sense ella, Nginx concatena la ruta completa i l'API rep /api/llibres, una ruta que server.js no defineix, d'aquí el 404 d'Express. Un sol caràcter, i la meitat dels proxies mal configurats del món s'expliquen per ell.

3. Restaurar i treure la publicació de l'API:

sed -i 's|proxy_pass $api_upstream;|proxy_pass $api_upstream/;|' ~/aurora-libros/web/nginx.conf
docker kill -s SIGHUP aurora-web

docker rm -f aurora-api
docker run -d --name aurora-api --network aurora-net \
  --label projecte=aurora-libros --label component=api \
  --env-file ~/aurora-libros/aurora.env \
  auroralibros/aurora-api:1.2.0

sleep 5
curl -s -o /dev/null -w "API directa (3000): HTTP %{http_code}\n" http://localhost:3000/llibres
curl -s http://localhost:8080/api/llibres | jq -r '.llibres | length'
API directa (3000): HTTP 000
8

Just el que es buscava: des del host, el port 3000 ja no existeix; des de la web, els vuit llibres continuen arribant. L'API ha deixat d'estar exposada i el servei no se n'ha ressentit, perquè Nginx hi arriba per la xarxa interna. És exactament l'arquitectura que voldries en producció: una sola porta d'entrada.

Com que a les lliçons següents convé poder cridar l'API directament per fer proves, torna a deixar-la publicada:

docker rm -f aurora-api
docker run -d --name aurora-api --network aurora-net \
  --label projecte=aurora-libros --label component=api \
  --env-file ~/aurora-libros/aurora.env -p 3000:3000 \
  auroralibros/aurora-api:1.2.0

Conclusió

El misteri està resolt i la plataforma està viva. Saps que cada contenidor té la seva pròpia pila de xarxa i el seu propi localhost, i que per això localhost:5432 dins de l'API preguntava per una base de dades que mai no va ser allà. Coneixes els drivers integrats —bridge per a gairebé tot, host quan no vols aïllament ni NAT, none per a aïllament total— i saps que overlay, macvlan i ipvlan existeixen per a escenaris de diversos hosts i d'integració amb la xarxa física que veuràs a les lliçons 05-01 i 06-03.

I tens clavada la diferència que ho explicava tot: la bridge per defecte no té DNS intern; una xarxa definida per l'usuari sí. Un docker network create aurora-net i un --network aurora-net a cada contenidor, i getent hosts aurora-db va passar de retornar codi 2 a retornar 172.18.0.2 aurora-db. Sense tocar una línia de server.js, ni el Dockerfile, ni el fitxer d'entorn. També saps connectar i desconnectar xarxes en calent per donar accés puntual a una eina d'administració, i posar àlies de xarxa a un contenidor.

Distingeixes publicar un port de comunicar dos contenidors: -p és perquè el host i l'exterior hi entrin; dins de la xarxa, tots els ports estan disponibles per nom sense publicar res. Per això aurora-cache ja no porta -p, aurora-db està lligada a 127.0.0.1, i has demostrat que fins i tot l'API pot viure sense publicació si Nginx és l'única porta d'entrada.

El resultat, en una línia: curl http://localhost:3000/llibres retorna els vuit llibres d'Aurora Libros, servits per Node des de PostgreSQL, desats a la memòria cau de Redis amb el seu TTL de 60 segons, i http://localhost:8080 mostra la llibreria completa al navegador a través d'un proxy invers que reenvia /api/ sense exposar l'API. El healthcheck marca healthy per primera vegada en tot el curs. Quatre contenidors, una xarxa, zero IPs escrites a mà.

Queda un problema, i ja l'has patit dues vegades en aquesta mateixa lliçó: cada vegada que recrees aurora-db, els vuit llibres desapareixen i has de tornar a carregar init.sql amb docker cp. Les dades viuen en un volum anònim que ningú no controla, o directament a la capa d'escriptura que mor amb el contenidor. A la lliçó següent, Persistència de Dades amb Volums, atacaràs aquest problema d'arrel: veuràs els tres tipus de muntatge —volums gestionats, bind mounts i tmpfs—, la diferència entre -v i --mount, on viuen realment els volums i per què no els has de tocar a mà. Donaràs a PostgreSQL el volum aurora-dades, muntaràs init.sql perquè s'executi tot sol, i repetiràs la prova definitiva: inserir un llibre nou, destruir el contenidor de la base de dades, recrear-lo, i comprovar que el llibre continua allà.

Docker: De Principiant a Avançat

Mòdul 1: Introducció a Docker

Mòdul 2: Treballant amb Imatges Docker

Mòdul 3: Contenidors Docker

Mòdul 4: Docker Compose

Mòdul 5: Conceptes Avançats de Docker

Mòdul 6: Docker en Producció

Mòdul 7: Ecosistema i Eines de Docker

© Copyright 2026. Tots els drets reservats