Aixecar la plataforma amb una comanda ja és una victòria. Però si per veure l'efecte de canviar una línia de server.js cal reconstruir la imatge i recrear el contenidor, ningú no farà servir Docker per programar: acabaran executant Node al host i tornarem als quinze passos.

Aquesta lliçó converteix el compose.override.yaml de desenvolupament en un entorn de treball diari: edites al teu editor, el canvi es veu a l'instant, depures pas a pas, executes proves aïllades i reinicies l'entorn amb una comanda. És l'última lliçó del mòdul 4.

Contingut

  1. L'objectiu i l'override de desenvolupament
  2. Bind mount del codi i el problema de node_modules
  3. Recàrrega automàtica del procés
  4. develop.watch: el mecanisme natiu de Compose
  5. docker compose watch en marxa
  6. Depuració pas a pas amb VS Code
  7. Accés a la base de dades
  8. Proves en contenidors efímers
  9. Dades de desenvolupament i reinici de l'entorn
  10. Rendiment dels bind mounts a macOS i Windows
  11. Les dreceres de l'equip: dev.sh i Makefile

  1. L'objectiu i l'override de desenvolupament

El cicle que busquem és: desar el fitxer a l'editor → el procés del contenidor es reinicia sol → recarregar el navegador. Sense build, sense up, sense esperes. Tot el que cal viu a compose.override.yaml, que Compose carrega automàticament en local (lliçó 04-06) i que al servidor no es fa servir mai.

# compose.override.yaml — entorn de desenvolupament d'Aurora Libros
services:

  aurora-api:
    build:
      context: ./api
      target: desenvolupament     # etapa del Dockerfile amb les devDependencies
    command: ["node", "--watch", "--inspect=0.0.0.0:9229", "src/server.js"]
    environment:
      NODE_ENV: development
      LOG_NIVELL: debug
    ports:
      - "3000:3000"
      - "9229:9229"               # inspector de Node
    volumes:
      - ./api/src:/app/src        # NOMÉS el codi, no tot el projecte
      - /app/node_modules         # volum anònim que protegeix les dependències
    restart: "no"

  aurora-db:
    ports:
      - "127.0.0.1:5432:5432"
    networks: [posterior, frontal]  # necessària per poder publicar el port

  1. Bind mount del codi i el problema de node_modules

El bind mount fa que el directori del host substitueixi el del contenidor. I aquí hi ha el parany clàssic: si muntes tot el projecte (./api:/app), el node_modules del host tapa el que va instal·lar la imatge durant el docker build.

Les conseqüències són de les que arruïnen una tarda:

  • Si no tens node_modules al host, el contenidor es queda sense dependències: Error: Cannot find module 'express'.
  • Si el tens, són les que va instal·lar el teu sistema operatiu i la teva versió de Node. Qualsevol paquet amb binaris natius compilats per a macOS no funciona dins d'una imatge Alpine.
docker compose exec aurora-api ls node_modules | head -3
ls: node_modules: No such file or directory
Opció Com Avantatges Inconvenients
Muntar només src - ./api/src:/app/src Senzilla i explícita; node_modules intacte Canviar package.json exigeix reconstruir
Volum anònim a sobre - ./api:/app + - /app/node_modules Es pot muntar tot el projecte El volum queda obsolet en canviar dependències
node_modules fora de l'arbre ENV NODE_PATH=/deps/node_modules al Dockerfile Impossible que col·lideixin Requereix tocar el Dockerfile
Instal·lar al host npm install local Res a configurar Trenca la premissa de Docker: binaris del host

La segona opció mereix una explicació, perquè el mecanisme no és obvi: el muntatge més específic guanya. Docker munta ./api sobre /app i, a sobre, un volum anònim sobre /app/node_modules, que en la seva primera creació s'inicialitza amb el contingut que la imatge ja tenia allà. El resultat és codi del host i dependències de la imatge.

Per a Aurora Libros fem servir la primera, que és la més predictible. I la regla que evita el 90 % dels problemes: quan canviïs package.json, reconstrueix.

docker compose up -d --build aurora-api
docker compose down -v && docker compose up -d --build   # si fas servir volum anònim

  1. Recàrrega automàtica del procés

Muntar el codi no n'hi ha prou: el procés node continua executant el que va carregar en arrencar. Node 22 porta la solució de sèrie:

    command: ["node", "--watch", "src/server.js"]

--watch vigila els fitxers importats i reinicia el procés en detectar un canvi. Ja no cal nodemon, encara que continua sent vàlid si necessites les seves opcions avançades (--watch-path, retards, execució de comandes prèvies).

docker compose logs -f aurora-api
# en un altre terminal, edita api/src/server.js i desa
aurora-api-1  | Aurora API escoltant al port 3000
aurora-api-1  | Restarting 'src/server.js'
aurora-api-1  | Aurora API escoltant al port 3000

Un avís sobre sistemes de fitxers: la propagació d'esdeveniments inotify a través d'un bind mount no sempre funciona a macOS i Windows. Si --watch no reacciona, fes servir --watch-preserve-output amb sondeig o el mecanisme de l'apartat següent, que no depèn d'inotify dins del contenidor.

  1. develop.watch: el mecanisme natiu de Compose

Compose incorpora el seu propi sistema de sincronització. La diferència clau: vigila des del host, no des de dins del contenidor, així que funciona igual a Linux, macOS i Windows.

    develop:
      watch:
        - action: sync                 # copiar fitxers al contenidor
          path: ./api/src
          target: /app/src
          ignore:
            - "**/*.test.js"
        - action: sync+restart         # copiar i reiniciar el contenidor
          path: ./api/config
          target: /app/config
        - action: rebuild              # reconstruir la imatge sencera
          path: ./api/package.json
Acció Què fa Quan fer-la servir
sync Copia els fitxers canviats al contenidor Codi font, amb recàrrega en calent al procés
sync+restart Copia i reinicia el contenidor Configuració que es llegeix en arrencar
rebuild Reconstrueix la imatge i recrea el contenidor package.json, Dockerfile, dependències
restart Només reinicia, sense copiar Canvis que ja són a dins per una altra via

El bloc complet per a Aurora Libros:

# compose.override.yaml
services:
  aurora-api:
    build: { context: ./api, target: desenvolupament }
    command: ["node", "--watch", "--inspect=0.0.0.0:9229", "src/server.js"]
    ports: ["3000:3000", "9229:9229"]
    develop:
      watch:
        - action: sync
          path: ./api/src
          target: /app/src
        - action: rebuild
          path: ./api/package.json
        - action: rebuild
          path: ./api/Dockerfile

  aurora-web:
    develop:
      watch:
        - action: sync+restart
          path: ./web/nginx.conf
          target: /etc/nginx/conf.d/default.conf
        - action: sync
          path: ./web/index.html
          target: /usr/share/nginx/html/index.html

Fixa't en el repartiment: la pàgina HTML només necessita copiar-se (sync), però nginx.conf es llegeix en arrencar, així que exigeix sync+restart. I package.json no es pot resoldre copiant: cal reconstruir.

  1. docker compose watch en marxa

docker compose watch
Watch enabled
 ⦿ Syncing "aurora-api" 1 file to /app/src
aurora-api-1  | Restarting 'src/server.js'
 ⦿ Rebuilding service "aurora-api" after changes were detected in package.json
 ✔ Container aurora-libros-aurora-api-1  Recreated
 ⦿ Syncing "aurora-web" 1 file to /usr/share/nginx/html

La comanda deixa el terminal ocupat mostrant la sincronització en viu. Amb --no-up no aixeca la pila, només vigila el que ja està aixecat; i si prefereixes veure-ho al costat dels logs, docker compose up --watch combina totes dues coses.

docker compose watch --no-up
docker compose up --watch

Un avantatge poc comentat de watch enfront del bind mount: pots fer servir el mateix Dockerfile i la mateixa imatge que en producció, perquè el codi no es munta, es copia. Això elimina tota una classe de diferències entre "funciona a la meva màquina" i el servidor.

  1. Depuració pas a pas amb VS Code

Amb --inspect=0.0.0.0:9229, Node obre el seu inspector. El 0.0.0.0 no és opcional: per defecte escolta només a 127.0.0.1 del contenidor, inabastable des del host.

docker compose logs aurora-api | grep -i debugger
aurora-api-1  | Debugger listening on ws://0.0.0.0:9229/8f2c...

.vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Adjuntar a Aurora API (Docker)",
      "type": "node",
      "request": "attach",
      "address": "localhost",
      "port": 9229,
      "localRoot": "${workspaceFolder}/api/src",
      "remoteRoot": "/app/src",
      "restart": true,
      "skipFiles": ["<node_internals>/**"]
    }
  ]
}

Les dues claus que gairebé tothom configura malament:

Clau Valor Per què importa
localRoot Ruta del codi a la teva màquina Tradueix les rutes dels punts d'interrupció
remoteRoot Ruta del codi al contenidor Sense la correspondència, els breakpoints surten "no verificats"
restart true Torna a enganxar el depurador després de cada reinici de --watch

Posa un punt d'interrupció al gestor de /llibres, llança la configuració i executa curl -s http://localhost:8080/api/llibres. L'execució s'atura i pots inspeccionar variables, la pila i la consola, amb el procés corrent dins del contenidor, amb els seus límits de memòria i la seva xarxa.

Avís de seguretat: el port 9229 dóna execució arbitrària de codi. Publica'l només en local ("127.0.0.1:9229:9229") i mai en un servidor.

  1. Accés a la base de dades

Dues vies, i convé tenir-les totes dues:

# Des del host, amb el teu client preferit (requereix el port publicat de l'override)
psql -h 127.0.0.1 -p 5432 -U aurora -d aurora_llibres

# Sense instal·lar res: el psql que ja viu al contenidor
docker compose exec aurora-db psql -U aurora -d aurora_llibres
docker compose exec aurora-db psql -U aurora -d aurora_llibres -c "\dt"
docker compose exec aurora-cache redis-cli KEYS 'llibres:*'
              List of relations
 Schema |  Name   | Type  |  Owner
--------+---------+-------+--------
 public | llibres | table | aurora
1) "llibres:tots"

Recorda de la lliçó 04-04 que aurora-db és a la xarxa posterior, que és internal: per publicar-ne el port, l'override l'afegeix també a frontal. I per a còpies ràpides, -T és obligatori:

docker compose exec -T aurora-db pg_dump -U aurora aurora_llibres > ~/aurora-copia.sql

  1. Proves en contenidors efímers

La manera directa, ja vista a la lliçó 04-03:

docker compose run --rm --no-deps aurora-api npm test

Però les proves d'integració necessiten una base de dades de debò i d'un sol ús. Un servei dedicat amb el seu PostgreSQL en tmpfs —és a dir, a RAM— resol les dues coses: aïllament total i una velocitat que el disc no dóna.

# compose.proves.yaml
name: aurora-proves

services:
  db-proves:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: aurora
      POSTGRES_PASSWORD: proves
      POSTGRES_DB: aurora_proves
    tmpfs:
      - /var/lib/postgresql/data      # TOT a RAM: s'evapora en aturar-se
    command: ["postgres", "-c", "fsync=off", "-c", "full_page_writes=off"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U aurora -d aurora_proves"]
      interval: 2s
      retries: 15

  proves:
    build: { context: ./api, target: desenvolupament }
    command: ["npm", "run", "test:integracio"]
    environment:
      DB_HOST: db-proves
      DB_USER: aurora
      DB_PASSWORD: proves
      DB_NAME: aurora_proves
      NODE_ENV: test
    depends_on:
      db-proves: { condition: service_healthy }
docker compose -f compose.proves.yaml up --abort-on-container-exit --exit-code-from proves
proves-1  | ✔ GET /llibres retorna el catàleg complet (48ms)
proves-1  | ✔ GET /llibres/:id retorna un títol (11ms)
proves-1  | ✔ POST /llibres rebutja un ISBN duplicat (23ms)
proves-1  | ℹ pass 12  fail 0
proves-1 exited with code 0

Tres peces encaixades: --abort-on-container-exit atura tot tan bon punt acaben les proves, --exit-code-from proves fa que la comanda retorni el codi de les proves (imprescindible a CI), i fsync=off és acceptable només perquè aquestes dades són d'un sol ús; en producció seria una barbaritat.

  1. Dades de desenvolupament i reinici de l'entorn

El servei llavors del perfil dades (lliçó 04-06) carrega un catàleg fictici ampliat sobre els nou títols d'init.sql:

docker compose --profile dades run --rm llavors
Inserits 40 títols de demostració. Catàleg: 49 llibres.

I el reinici complet, la maniobra que faràs més vegades quan alguna cosa s'embolica:

docker compose down -v && docker compose up -d --wait && \
  docker compose --profile dades run --rm llavors

Seixanta segons i tens un entorn idèntic al de la resta de l'equip. Aquí -v és correcte i desitjable: són dades de desenvolupament generades a partir de fitxers versionats. És l'únic context en què aquesta comanda no t'ha de fer por.

  1. Rendiment dels bind mounts a macOS i Windows

A Linux, un bind mount és una operació del nucli: cost zero. A macOS i Windows, Docker corre dins d'una màquina virtual lleugera i cada lectura travessa una frontera entre sistemes de fitxers. Amb node_modules —desenes de milers de fitxers petits— la diferència es nota molt.

Plataforma Situació Mitigació
Linux Natiu, sense penalització Cap de necessària
macOS Traducció host ↔ VM VirtioFS activat a Docker Desktop (per defecte des del 2023)
Windows + WSL 2 Ràpid si el codi és dins de WSL Desar el repositori a ~/projectes, mai a /mnt/c/...
Windows sense WSL 2 Molt lent Migrar a WSL 2
Qualsevol node_modules compartit No muntar-lo: volum anònim o sync de watch

L'error de rendiment més freqüent a Windows és tenir el repositori a C:\Users\... i accedir-hi des de WSL per /mnt/c/: cada require() travessa dues capes de traducció. Moure el projecte al sistema de fitxers de Linux multiplica la velocitat per deu.

Les opcions :cached i :delegated que veuràs en documentació antiga eren ajustos de consistència per a l'antic osxfs. Avui s'accepten i s'ignoren: VirtioFS les ha deixat obsoletes. I la millor mitigació continua sent estructural: docker compose watch copia en comptes de muntar, així que evita el problema d'arrel.

  1. Les dreceres de l'equip: dev.sh i Makefile

Ningú no recorda docker compose -f compose.yaml -f compose.proves.yaml up --abort-on-container-exit --exit-code-from proves. Posa-ho en un fitxer i documenta l'entorn de passada:

# Makefile — dreceres d'Aurora Libros
.PHONY: amunt avall logs sh psql proves llavors reset watch

amunt:         ## Aixeca la plataforma i espera que estigui sana
	docker compose up -d --build --wait

avall:         ## Atura la plataforma (conserva les dades)
	docker compose --profile eines down

logs:          ## Segueix els logs de tots els serveis
	docker compose logs -f --tail 100

watch:         ## Desenvolupament amb sincronització automàtica
	docker compose watch

sh:            ## Shell dins de l'API
	docker compose exec aurora-api sh

psql:          ## Consola de PostgreSQL
	docker compose exec aurora-db psql -U aurora -d aurora_llibres

proves:        ## Proves d'integració amb base de dades efímera
	docker compose -f compose.proves.yaml up \
		--abort-on-container-exit --exit-code-from proves

llavors:       ## Carrega el catàleg de demostració
	docker compose --profile dades run --rm llavors

reset:         ## ESBORRA les dades i reconstrueix l'entorn des de zero
	docker compose down -v
	docker compose up -d --build --wait
	docker compose --profile dades run --rm llavors
make amunt
make watch
make reset

Dos detalls no negociables: en un Makefile la indentació és amb tabulador, i l'objectiu destructiu es diu reset —no netejar, no down— perquè ningú no el teclegi per inèrcia. Si prefereixes un dev.sh, la idea és la mateixa: les comandes de l'equip, a Git, al costat del codi.

Errors Habituals i Consells

Muntar tot el projecte sobre /app sense protegir node_modules. El node_modules del host tapa el de la imatge i apareixen mòduls que no existeixen o binaris d'una altra plataforma.

Canviar package.json i esperar que n'hi hagi prou amb desar. Instal·lar dependències exigeix reconstruir: up -d --build o una regla rebuild a develop.watch.

Fer servir --inspect sense 0.0.0.0. L'inspector escolta només dins del contenidor i VS Code no pot connectar-s'hi.

Deixar el 9229 publicat a totes les interfícies. És execució remota de codi. 127.0.0.1:9229:9229 i només en local.

Posar les comoditats de desenvolupament al compose.yaml base. Un build o un port de depuració acaben colant-se al servidor.

Treballar des de /mnt/c/ a WSL 2. El rendiment cau en picat. El repositori va al sistema de fitxers de Linux.

Consell: si el reinici automàtic no es dispara, l'ordre de diagnòstic és: és el fitxer realment dins de la ruta muntada o vigilada? (docker compose exec aurora-api ls -la src/), el procés arrenca amb --watch? (docker compose exec aurora-api ps aux), i si ets a macOS o Windows, canvia a docker compose watch, que no depèn d'inotify dins del contenidor.

Exercicis

Exercici 1. Munta ./api/src a aurora-api, arrenca amb node --watch i demostra el cicle complet: canvia el missatge de /salut al teu editor i comprova amb curl que la resposta canvia sense reconstruir ni recrear el contenidor. Verifica també que el contenidor és el mateix d'abans.

Exercici 2. Configura develop.watch amb les tres accions (sync per a src, sync+restart per a un fitxer de configuració i rebuild per a package.json), llança docker compose watch i provoca-les totes tres. Explica què observes en cada cas i per què cada fitxer necessita una acció diferent.

Exercici 3. Munta l'entorn de proves amb PostgreSQL en tmpfs, executa'l dues vegades seguides i demostra que (a) la segona execució parteix d'una base de dades completament neta i (b) la comanda retorna el codi de sortida de les proves, no el del contenidor de la base de dades.

Solucions

Solució 1.

docker compose up -d --build
docker inspect --format '{{.Id}}' aurora-libros-aurora-api-1 | cut -c1-12
curl -s http://localhost:3000/salut | jq -r '.missatge'
c4f1a9e0b73d
Aurora API operativa

Edita api/src/server.js canviant el missatge a "Aurora API operativa i recarregada" i desa:

sleep 2
curl -s http://localhost:3000/salut | jq -r '.missatge'
docker inspect --format '{{.Id}}' aurora-libros-aurora-api-1 | cut -c1-12
docker compose logs aurora-api --tail 2
Aurora API operativa i recarregada
c4f1a9e0b73d
aurora-api-1  | Restarting 'src/server.js'
aurora-api-1  | Aurora API escoltant al port 3000

L'identificador del contenidor és idèntic: no s'ha recreat res. L'única cosa que ha passat és que el procés node dins del contenidor s'ha reiniciat en detectar el canvi en un fitxer que, gràcies al bind mount, és el mateix inode que el del teu editor. Aquest és el cicle de desenvolupament que buscàvem: dos segons entre desar i veure'n l'efecte.

Solució 2.

docker compose watch
Canvi provocat Sortida observada Per què aquesta acció
Editar src/server.js Syncing "aurora-api" 1 file i Restarting 'src/server.js' El fitxer només cal copiar-lo; la recàrrega la fa --watch
Editar config/ajustos.json Syncing ... Restarting service La configuració es llegeix en arrencar: copiar no n'hi ha prou
Editar package.json Rebuilding service "aurora-api" i Container ... Recreated Una dependència nova exigeix npm install, que només passa al build

La progressió és de menor a major cost: sync triga mil·lisegons, sync+restart uns segons, rebuild desenes de segons. Per això convé reservar rebuild per als fitxers que de debò ho necessiten —package.json, package-lock.json, Dockerfile— i no apuntar-lo a un directori sencer: una regla rebuild sobre ./api reconstruiria la imatge cada vegada que toques una línia de codi.

Solució 3.

docker compose -f compose.proves.yaml up --abort-on-container-exit --exit-code-from proves
echo "codi de la primera execució: $?"
docker compose -f compose.proves.yaml down
docker compose -f compose.proves.yaml up --abort-on-container-exit --exit-code-from proves
echo "codi de la segona execució: $?"
docker volume ls --filter name=aurora-proves --format "{{.Name}}" | wc -l
proves-1  | ℹ pass 12  fail 0
codi de la primera execució: 0
proves-1  | ℹ pass 12  fail 0
codi de la segona execució: 0
0

(a) La segona execució dóna exactament els mateixos resultats i no existeix cap volum: el tmpfs viu a la memòria del host i desapareix en eliminar el contenidor, així que cada execució parteix d'una base de dades acabada d'inicialitzar. És la propietat que fa que unes proves d'integració siguin fiables: si l'execució número cinquanta pot fallar per residus de la quaranta-nou, les proves no valen res.

(b) --exit-code-from proves és el que fa que això sigui utilitzable a CI. Sense aquesta opció, up retorna el codi del primer contenidor que acaba, que pot ser el de la base de dades aturant-se ordenadament amb un 0 alegre mentre les proves fallaven. Comprova-ho fent fallar una prova expressament: el codi passa a 1 i el pipeline es posa en vermell, que és justament el que ha de passar.

Conclusió

Docker ha deixat de ser una cosa que es fa servir "al final, per empaquetar" i s'ha convertit en l'entorn on programes. Saps muntar el codi amb un bind mount i esquivar el problema etern de node_modules —el del host tapant el de la imatge— amb les quatre estratègies possibles i la seva regla d'or: si canvia package.json, cal reconstruir. Tens recàrrega automàtica amb el --watch natiu de Node 22, i per damunt el mecanisme propi de Compose, develop.watch, amb les seves accions graduades per cost: sync per al codi, sync+restart per a la configuració que es llegeix en arrencar i rebuild per a les dependències, vigilant des del host i funcionant igual a les tres plataformes.

Depures de debò: --inspect=0.0.0.0:9229, el port publicat només a 127.0.0.1, i un launch.json amb la correspondència localRoot/remoteRoot que fa que els punts d'interrupció s'aturin al codi que corre dins del contenidor. Entres a la base de dades des del host o amb docker compose exec, executes proves d'integració contra un PostgreSQL efímer en tmpfs amb --abort-on-container-exit i --exit-code-from, carregues dades fictícies amb un servei de llavors i reinicies l'entorn sencer amb una comanda. I coneixes el perquè de la lentitud a macOS i Windows —la frontera entre el host i la màquina virtual— amb les seves mitigacions reals: VirtioFS, el codi dins del sistema de fitxers de WSL 2 i, sobretot, no muntar node_modules. Tot plegat resumit en un Makefile versionat que qualsevol de l'equip pot llegir.

I amb això es tanca el mòdul 4. Vas començar amb cinquanta línies de comandes imperatives encadenades, fràgils i sense versionar, i acabes amb la plataforma sencera declarada en un fitxer de text: quatre serveis més una migració, dues xarxes segmentades amb la zona de dades aïllada de l'exterior, un volum persistent, sondes de salut que distingeixen "arrencat" de "preparat", dependències que respecten aquest matís, límits de recursos, polítiques de reinici, variables parametritzades amb el seu .env.example versionat i els seus secrets fora de l'entorn, perfils per a les eines opcionals i overrides que fan que el mateix projecte serveixi al teu portàtil, a CI i al servidor. Tot revisable en una pull request, tot reproduïble. Els quinze passos d'onboarding de la lliçó 01-07 són avui exactament dos: git clone i docker compose up -d --wait.

A partir d'aquí, el curs canvia de registre. Fins ara has après a fer servir Docker molt bé; al mòdul 5 obriràs la caixa negra i entendràs per què funciona com funciona: les xarxes per dins, amb els seus drivers, taules de rutes i regles d'iptables; l'emmagatzematge a fons, amb controladors i estratègies de còpia de seguretat; la seguretat real, amb usuaris sense privilegis, capacitats del nucli, seccomp i anàlisi de vulnerabilitats; l'optimització d'imatges, on aquests 274 MB de PostgreSQL i aquests 142 MB de l'API es posen a dieta amb construccions multietapa; BuildKit i Buildx, amb memòria cau remota i compilació multiarquitectura; el registre i el monitoratge d'una plataforma en marxa; i, al final, els namespaces, cgroups i capes del nucli de Linux que fa temps que sostenen tot el que has fet des de la primera lliçó. Quan acabis aquest mòdul, ja no seràs algú que sap escriure un compose.yaml: seràs algú que sap què passa exactament quan l'executa.

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