Aurora Libros ja està declarada i parametritzada, però a la vida real la mateixa plataforma s'aixeca de tres maneres diferents: al portàtil de qui programa, al runner d'integració contínua i al servidor. Les diferències són poques —construir en comptes de descarregar, exposar o no certs ports, límits més alts— i la temptació és duplicar el fitxer.
Duplicar-lo és el pitjor camí: dos fitxers divergeixen en una setmana. Compose ofereix dos mecanismes complementaris per evitar-ho: perfils, que activen serveis opcionals sota demanda, i fitxers d'override, que es fusionen sobre una base comuna.
Contingut
- Perfils: serveis que no sempre vols
- Perfils i dependències: les regles
- L'override automàtic:
compose.override.yaml - Overrides explícits amb diversos
-f - Les regles de fusió
!reseti!override: substituir en comptes de fusionar- L'estratègia d'Aurora Libros: base, desenvolupament i producció
extends: reutilitzar definicions entre projectesinclude:: compondre fitxers de diversos equips- Convencions de noms de fitxer i de projecte
- Perfils: serveis que no sempre vols
Un servei amb la clau profiles: no s'aixeca llevat que el seu perfil estigui actiu. És la manera de tenir eines opcionals al mateix fitxer sense que molestin.
adminer:
image: adminer:5
profiles: [eines]
ports:
- "${ADMINER_PORT:-8081}:8080"
environment:
ADMINER_DEFAULT_SERVER: aurora-db
depends_on:
aurora-db: { condition: service_healthy }
networks: [frontal, posterior]
mailhog:
image: mailhog/mailhog:v1.0.1
profiles: [eines]
ports:
- "8025:8025" # interfície web per llegir els correus capturats
networks: [frontal]
llavors:
image: auroralibros/aurora-api:${AURORA_API_VERSION:-1.2.0}
profiles: [dades]
command: ["node", "llavors.js", "--cataleg-demo"]
environment:
DB_HOST: aurora-db
DB_USER: ${DB_USUARI:-aurora}
DB_NAME: ${DB_NOM:-aurora_llibres}
depends_on:
aurora-db: { condition: service_healthy }
restart: "no"
networks: [posterior]Un servei pot pertànyer a diversos perfils (profiles: [eines, ci]), i s'activa si algun d'ells està actiu.
docker compose up -d # només els 5 serveis base
docker compose --profile eines up -d # base + adminer + mailhog
docker compose --profile eines --profile dades up -d # tot
COMPOSE_PROFILES=eines,dades docker compose up -d # equivalent
docker compose --profile "*" up -d # tots els perfilsaurora-api
aurora-cache
aurora-db
aurora-migracions
aurora-web
(amb el perfil eines)
adminer
aurora-api
aurora-cache
aurora-db
aurora-migracions
aurora-web
mailhogAmb Adminer aixecat, http://localhost:8081 et dóna una interfície web per explorar el catàleg sense instal·lar res a la teva màquina. I COMPOSE_PROFILES=eines al teu .env local activa el perfil permanentment per a tu, sense afectar ningú més.
Un ús molt pràctic d'un perfil per a tasques puntuals:
- Perfils i dependències: les regles
Aquí hi ha tres comportaments que convé tenir clars, perquè són font de sorpreses:
| Situació | Què fa Compose |
|---|---|
| Servei amb perfil, perfil inactiu | No es crea, ni tan sols si un altre el té a depends_on |
| Servei sense perfil que depèn d'un amb perfil | Error: depends_on a un servei no habilitat |
| Servei amb perfil que depèn d'un sense perfil | Correcte: la dependència s'aixeca automàticament |
S'anomena el servei explícitament (up adminer) |
El seu perfil s'activa sol, sense --profile |
down sense --profile |
No atura els serveis amb perfil actiu |
Les dues files importants: anomenar un servei amb perfil l'activa implícitament —docker compose up -d adminer funciona sense més—, i docker compose down deixa orfes els serveis de perfil si no repeteixes el perfil. És la causa número u de "he fet down i encara hi ha contenidors".
docker compose --profile eines down # correcte
docker compose down --remove-orphans # alternativa contundentRegla de disseny: els serveis base mai no han de dependre d'un servei amb perfil. La dependència va sempre en la direcció contrària.
- L'override automàtic:
compose.override.yaml
compose.override.yamlSi existeix un fitxer anomenat compose.override.yaml (o .yml) al costat del compose.yaml, Compose el carrega i el fusiona automàticament, sense que hagis d'indicar res.
docker compose up -d
# equival exactament a:
docker compose -f compose.yaml -f compose.override.yaml up -dÉs el mecanisme perfecte per a l'entorn de desenvolupament: el compose.yaml descriu la plataforma de manera neutra i l'override hi afegeix el que només té sentit a la teva màquina. I com que l'override s'ignora al servidor —on es fan servir altres fitxers explícits—, no hi ha risc que un port de depuració acabi en producció.
Quan passis fitxers amb -f, l'override automàtic deixa de carregar-se: manes tu.
- Overrides explícits amb diversos
-f
-fCompose llegeix els fitxers en l'ordre indicat i fusiona cadascun sobre el resultat acumulat: l'últim guanya. L'ordre importa i és una font clàssica d'errors; si inverteixes els fitxers, el fitxer base sobreescriu l'específic.
Les rutes relatives es resolen respecte al directori del primer fitxer, llevat que facis servir --project-directory. Si els teus overrides viuen en un subdirectori, tingues-ho present:
- Les regles de fusió
El que passa en fusionar depèn del tipus de cada clau:
| Tipus de clau | Exemples | Comportament |
|---|---|---|
| Escalar | image, restart, user, container_name |
Substitueix: guanya l'últim fitxer |
| Mapa | environment (forma mapa), labels, deploy, healthcheck |
Es fusionen clau a clau: guanya l'últim a les coincidents, es conserven les altres |
| Llista | ports, volumes, dns, env_file, networks |
Es concatenen: apareixen els elements de tots dos |
| Llista tractada com a bloc | command, entrypoint, healthcheck.test |
Substitueix sencera: no es concatena |
environment en forma de llista |
- CLAU=valor |
Es fusiona per nom de variable, no es duplica |
La distinció entre les dues primeres files i la tercera explica el 90 % de les sorpreses. Un exemple:
# compose.yaml
aurora-api:
image: auroralibros/aurora-api:1.2.0
environment:
NODE_ENV: production
LOG_NIVELL: info
ports:
- "3000:3000"environment s'ha fusionat (NODE_ENV sobreviu, LOG_NIVELL se substitueix) i ports s'ha concatenat (els dos ports). Que les llistes es concatenin és còmode per afegir, però significa que no en pots treure un port o un volum des d'un override... llevat del que ve ara.
!reset i !override: substituir en comptes de fusionar
!reset i !override: substituir en comptes de fusionarCompose v2.24 va introduir dues etiquetes YAML que resolen justament aquest problema:
# compose.prod.yaml
services:
aurora-api:
ports: !reset [] # elimina TOTS els ports heretats
volumes: !override # substitueix la llista en comptes de concatenar-la
- aurora-logs:/app/logs!reset buida la clau heretada (i amb null l'elimina per complet), i !override substitueix el valor en comptes de fusionar-lo. Són l'única manera neta de treure en un override el que la base hi afegeix, i eviten l'antic apedaçament de mantenir dos fitxers base gairebé idèntics.
- L'estratègia d'Aurora Libros: base, desenvolupament i producció
L'estructura recomanada són tres fitxers amb responsabilitats ben separades:
| Fitxer | Contingut | Quan es fa servir |
|---|---|---|
compose.yaml |
La plataforma neutra: serveis, xarxes, volums, dependències, sondes | Sempre |
compose.override.yaml |
Comoditats de desenvolupament | Automàtic en local |
compose.prod.yaml |
Enduriment i ajustos de servidor | Explícit amb -f |
El base és el de la lliçó 04-04, amb una regla afegida: res específic d'un entorn. Ni build, ni ports de depuració, ni NODE_ENV fixat a mà.
# compose.override.yaml — desenvolupament local (automàtic)
services:
aurora-api:
build: # construir des del codi, no descarregar
context: ./api
dockerfile: Dockerfile
environment:
NODE_ENV: development
LOG_NIVELL: debug
ports:
- "3000:3000" # atacar l'API directament
- "9229:9229" # inspector de Node (lliçó 04-07)
volumes:
- ./api/src:/app/src # codi muntat des del host
restart: "no" # que una fallada no s'amagui en un bucle
aurora-db:
ports:
- "127.0.0.1:5432:5432" # psql des del host
networks: [posterior, frontal] # necessària per poder publicar el port
adminer:
profiles: [eines]
image: adminer:5
ports: ["8081:8080"]
environment: { ADMINER_DEFAULT_SERVER: aurora-db }
networks: [frontal, posterior]El detall dels bind mounts de codi i de la recàrrega automàtica és la lliçó 04-07; aquí només interessa on viu aquesta configuració: a l'override, mai a la base.
# compose.prod.yaml — servidor
services:
aurora-api:
image: auroralibros/aurora-api:${AURORA_API_VERSION:?fixa la versió a desplegar}
build: !reset null # al servidor no es construeix: només es descarrega
environment:
NODE_ENV: production
LOG_NIVELL: warn
ports: !reset [] # sense accés directe: tot passa pel proxy
restart: always
deploy:
resources: { limits: { memory: 512M, cpus: "2.0" } }
logging:
driver: json-file
options: { max-size: "50m", max-file: "5" }
aurora-db:
restart: always
deploy:
resources: { limits: { memory: 2G, cpus: "2.0" }, reservations: { memory: 1G } }
aurora-web:
restart: always
ports:
- "80:80"
deploy:
resources: { limits: { memory: 256M, cpus: "1.0" } }Fixa't en tres decisions: la versió de la imatge és obligatòria (:?), perquè desplegar latest és desplegar qualsevol cosa; build: !reset null garanteix que el servidor no construeixi mai; i ports: !reset [] elimina el 3000 heretat, no l'hi afegeix.
# Desenvolupament: l'override es carrega sol
docker compose up -d --build
# Producció
docker compose -f compose.yaml -f compose.prod.yaml -p aurora-prod up -d --wait
# CI: ni override de desenvolupament ni ajustos de servidor
docker compose -f compose.yaml -f compose.ci.yaml up -d --waitVerifica sempre un desplegament abans d'executar-lo:
extends: reutilitzar definicions entre projectes
extends: reutilitzar definicions entre projectesMentre que els overrides fusionen fitxers complets, extends importa un servei concret, fins i tot des d'un altre fitxer o projecte:
# comuns/serveis-base.yaml
services:
node-base:
image: node:22-alpine
working_dir: /app
user: "1001:1001"
init: true
restart: unless-stopped# compose.yaml
services:
aurora-api:
extends:
file: comuns/serveis-base.yaml
service: node-base
image: auroralibros/aurora-api:1.2.0 # el que és local guanya
environment:
PORT: "3000"| Avantatge | Limitació |
|---|---|
| Comparteix definicions entre projectes diferents | No importa depends_on, volumes_from ni links |
No exigeix ordre de -f |
Només un extends per servei, encara que es pot encadenar |
| Documenta explícitament l'origen | Les rutes relatives es resolen des del fitxer estès |
La limitació de depends_on és deliberada: una dependència només té sentit dins del projecte que la defineix. Fes servir extends per a plantilles de configuració (imatge base, usuari, política de reinici) i àncores YAML per a la repetició dins del mateix fitxer.
include:: compondre fitxers de diversos equips
include:: compondre fitxers de diversos equipsinclude: va un pas més enllà: incorpora fitxers de Compose complets, amb els seus serveis, xarxes i volums, com si estiguessin escrits al teu.
# compose.yaml
include:
- path: ../plataforma-comuna/compose.observabilitat.yaml
- path: ./pagaments/compose.yaml
env_file: ./pagaments/.env # cada fitxer inclòs resol LES SEVES variables
project_directory: ./pagaments # i les seves rutes relatives
services:
aurora-api:
depends_on:
- collector-metriques # servei definit al fitxer inclòsLa diferència amb -f és important: amb diversos -f els fitxers es fusionen (s'espera que parlin dels mateixos serveis), mentre que include agrega fitxers independents que aporten serveis propis. Cada fitxer inclòs manté el seu propi context de rutes i de variables, la qual cosa permet que l'equip de pagaments mantingui el seu compose.yaml sense coordinar-se amb el de plataforma. A canvi, els noms de servei han de ser únics en el conjunt.
- Convencions de noms de fitxer i de projecte
| Fitxer | Ús |
|---|---|
compose.yaml |
Base neutra. Sempre |
compose.override.yaml |
Desenvolupament local. Automàtic |
compose.prod.yaml |
Servidor |
compose.ci.yaml |
Integració contínua |
compose.proves.yaml |
Proves d'integració amb dades efímeres |
I el nom de projecte per entorn, perquè dues piles convisquin a la mateixa màquina sense trepitjar-se:
docker compose -p aurora-dev up -d
docker compose -f compose.yaml -f compose.prod.yaml -p aurora-prod up -d
docker compose lsNAME STATUS CONFIG FILES
aurora-dev running(5) /home/joan/aurora-libros/compose.yaml,...override.yaml
aurora-prod running(5) /home/joan/aurora-libros/compose.yaml,...prod.yamlDues piles completes, amb contenidors, xarxes i volums prefixats de manera diferent, sense compartir ni una sola dada. L'única cosa que continua sent global de la màquina són els ports publicats: per això l'override de desenvolupament fa servir el 8080 i el de producció, el 80.
Per no teclejar els -f cent vegades al dia, fixa el conjunt a l'entorn:
# .env.prod (carregat amb --env-file, o exportat al servidor)
COMPOSE_FILE=compose.yaml:compose.prod.yaml
COMPOSE_PROJECT_NAME=aurora-prodErrors Habituals i Consells
Invertir l'ordre dels -f. -f compose.prod.yaml -f compose.yaml fa que la base sobreescrigui producció. L'específic va sempre l'últim.
Esperar que un override tregui un port. Les llistes es concatenen. Per eliminar, !reset o !override.
Fer down sense repetir el perfil. Els serveis amb perfil es queden corrent. Repeteix --profile o fes servir --remove-orphans.
Posar build al fitxer base. Un servidor acabarà construint la imatge en comptes de descarregar la versió provada. build va a l'override de desenvolupament.
Desplegar sense fixar l'etiqueta de la imatge. latest en producció significa que dos servidors poden estar corrent codi diferent. Fes servir ${AURORA_API_VERSION:?...}.
Suposar que extends porta les dependències. No importa depends_on: cal tornar-lo a declarar al servei que estén.
Consell: abans de qualsevol desplegament, docker compose -f ... config i llegeix-lo. Trenta segons de lectura eviten desplegar un port de depuració obert a Internet.
Exercicis
Exercici 1. Crea un compose.override.yaml que, per a aurora-api, canviï LOG_NIVELL a debug, hi afegeixi el port 9229 i munti ./api/src. Sense aixecar res, demostra amb docker compose config que el NODE_ENV heretat sobreviu, que hi ha dos ports i que el volum apareix al costat dels de la base.
Exercici 2. Afegeix un servei adminer sota el perfil eines i respon amb comandes: (a) apareix a docker compose ps --services sense el perfil?, (b) què passa si l'anomenes explícitament a up sense --profile?, i (c) què passa en fer docker compose down sense el perfil? Proposa la manera correcta d'abaixar-ho tot.
Exercici 3. Escriu un compose.prod.yaml que elimini per complet la publicació del port 3000 de l'API, exigeixi la variable AURORA_API_VERSION i impedeixi construir al servidor. Demostra amb config que el resultat no conté ni build ni el port 3000, i que falla de manera clara si no es defineix la versió.
Solucions
Solució 1.
# compose.override.yaml
services:
aurora-api:
environment:
LOG_NIVELL: debug
ports:
- "9229:9229"
volumes:
- ./api/src:/app/srcdocker compose config | sed -n '/aurora-api:/,/aurora-cache:/p' | grep -E "NODE_ENV|LOG_NIVELL|published|source" LOG_NIVELL: debug
NODE_ENV: production
published: "3000"
published: "9229"
source: /home/joan/aurora-libros/api/srcEs veuen les tres regles en acció: environment és un mapa, així que NODE_ENV sobreviu i només se substitueix la clau coincident; ports i volumes són llistes, així que es concatenen. Fixa't també que config ha normalitzat la ruta relativa ./api/src a ruta absoluta: aquesta normalització és justament el que fa fiable la revisió prèvia a un desplegament.
Solució 2.
# (a)
docker compose ps --services | grep adminer || echo "adminer NO està actiu"
# (b)
docker compose up -d adminer
docker compose ps --services | grep adminer
# (c)
docker compose down
docker ps --format "{{.Names}}" | grep adminer(a) Sense el perfil, el servei ni tan sols es considera. (b) Anomenar-lo l'activa implícitament: no cal --profile si el demanes pel seu nom. (c) I aquí la sorpresa: després de docker compose down, adminer continua viu, perquè down sense perfil no el contempla. És un contenidor orfe que continua ocupant el port 8081 i amb accés a la base de dades.
docker compose --profile eines down # forma correcta
docker compose down --remove-orphans # alternativa que ho escombra totSolució 3.
# compose.prod.yaml
services:
aurora-api:
image: auroralibros/aurora-api:${AURORA_API_VERSION:?fixa la versió a desplegar}
build: !reset null
ports: !reset []
environment:
NODE_ENV: production
LOG_NIVELL: warn
restart: alwaysAURORA_API_VERSION=1.2.0 docker compose -f compose.yaml -f compose.prod.yaml config \
| sed -n '/aurora-api:/,/aurora-cache:/p' | grep -E "image:|build|3000" || echo "sense build ni 3000"
unset AURORA_API_VERSION
docker compose -f compose.yaml -f compose.prod.yaml config --quiet; echo "codi: $?" image: auroralibros/aurora-api:1.2.0
error while interpolating services.aurora-api.image: required variable
AURORA_API_VERSION is missing a value: fixa la versió a desplegar
codi: 1La imatge queda fixada a una etiqueta concreta, no apareixen ni build ni el port 3000 —!reset els ha eliminat, cosa que una simple redefinició no hauria aconseguit amb les llistes— i sense la variable el desplegament falla abans de tocar res, amb un missatge que diu què cal fer. Aquesta fallada primerenca és exactament el que vols en un pipeline: millor un config --quiet en vermell que un servidor servint una versió imprevista.
Conclusió
Un mateix projecte serveix ja per a tres entorns sense duplicar ni un sol servei. Els perfils et donen serveis opcionals sota demanda —adminer i mailhog sota eines, llavors sota dades—, activables amb --profile, amb COMPOSE_PROFILES o simplement anomenant el servei, amb dues regles que s'obliden: un servei base mai no ha de dependre d'un amb perfil, i down sense repetir el perfil deixa contenidors orfes corrent.
Els overrides cobreixen la resta: el compose.override.yaml automàtic per a les comoditats de desenvolupament i la combinació explícita amb diversos -f on l'últim guanya. Domines les regles de fusió —escalars que se substitueixen, mapes que es fusionen clau a clau, llistes que es concatenen i blocs com command o healthcheck.test que es reemplacen sencers— i saps sortir del carreró de les llistes amb !reset i !override, la manera neta de treure un port de depuració en producció. L'estratègia queda clara: un compose.yaml neutre sense build ni ports de depuració, un override de desenvolupament i un compose.prod.yaml amb etiqueta d'imatge obligatòria, restart: always, límits propis i logging configurat; i docker compose config llegit abans de cada desplegament.
Saps a més quan fer servir cada eina de reutilització: àncores YAML dins d'un fitxer, extends per a plantilles de servei entre projectes —recordant que no arrossega depends_on—, i include: per agregar fitxers complets que mantenen altres equips, cadascun amb el seu context de rutes i variables. I la convenció de noms de fitxer i de projecte (-p aurora-dev, -p aurora-prod) que permet que dues piles convisquin a la mateixa màquina sense compartir ni un byte, amb COMPOSE_FILE per no repetir els -f.
Queda la part que més faràs servir: el dia a dia. A la lliçó següent, Desenvolupament Local amb Docker Compose, convertiràs aquest override de desenvolupament en un entorn de treball real: bind mount del codi amb la solució a l'etern problema de node_modules, recàrrega automàtica amb --watch de Node 22 i amb el bloc natiu develop.watch de Compose, depuració pas a pas des de VS Code amb l'inspector al port 9229, proves en contenidors efímers amb la seva base de dades en tmpfs, dades de desenvolupament i reinici en una comanda, el rendiment dels bind mounts a macOS i Windows, i un Makefile amb les dreceres de l'equip.
Docker: De Principiant a Avançat
Mòdul 1: Introducció a Docker
- Què és Docker?
- Instal·lant Docker
- Arquitectura de Docker
- Comandes Bàsiques de Docker
- Entenent les Imatges de Docker
- Creant el teu Primer Contenidor Docker
- El Projecte del Curs: la Plataforma Aurora Libros
Mòdul 2: Treballant amb Imatges Docker
- Docker Hub i Repositoris
- Construint Imatges Docker
- Conceptes Bàsics de Dockerfile
- Instruccions Avançades del Dockerfile
- Gestionant Imatges Docker
- Etiquetatge i Publicació d'Imatges
Mòdul 3: Contenidors Docker
- Executant Contenidors
- Cicle de Vida del Contenidor
- Gestionant Contenidors
- Inspecció i Depuració de Contenidors
- Xarxes a Docker
- Persistència de Dades amb Volums
- Límits de Recursos i Polítiques de Reinici
Mòdul 4: Docker Compose
- Introducció a Docker Compose
- Definint Serveis a Docker Compose
- Comandes de Docker Compose
- Aplicacions Multi-Contenidor
- Variables d'Entorn a Docker Compose
- Perfils, Overrides i Múltiples Entorns
- Desenvolupament Local amb Docker Compose
Mòdul 5: Conceptes Avançats de Docker
- Aprofundiment en Xarxes Docker
- Opcions d'Emmagatzematge Docker
- Millors Pràctiques de Seguretat a Docker
- Optimitzant Imatges Docker
- Builds Avançades amb BuildKit i Buildx
- Registre i Monitoratge a Docker
- El Runtime per Dins: Namespaces, Cgroups i Capes
Mòdul 6: Docker en Producció
- Preparar una Imatge per a Producció
- CI/CD amb Docker
- Orquestrant Contenidors amb Docker Swarm
- Introducció a Kubernetes
- Desplegant Contenidors Docker a Kubernetes
- Escalat i Balanceig de Càrrega
- Estratègies de Desplegament i Rollback
