La teva imatge auroralibros/aurora-api:1.1.0 està publicada i és impecable: usuari sense privilegis, metadades OCI, healthcheck i aturada neta en 0,3 segons. I continua retornant ECONNREFUSED a /llibres, exactament com t'anunciava la lliçó anterior. Aquest mòdul sencer existeix per arreglar això, i comença per on cal començar: entendre de debò la comanda que fas servir des de la lliçó 01-06 sense haver-la mirat mai de prop. docker run no és una comanda, són dues: crea un contenidor i l'arrenca. I admet més d'un centenar d'opcions, de les quals a la feina diària se'n fan servir unes quinze. En aquesta lliçó veuràs què fa run per dins, aprendràs aquestes quinze opcions agrupades per famílies, sobreescriuràs el CMD i l'ENTRYPOINT d'una imatge des de la línia de comandes, i aixecaràs per fi aurora-db i aurora-cache com a contenidors reals. En acabar tindràs tres contenidors vius... i l'API continuarà sense funcionar. Aquesta fallada, amb el missatge d'error canviat, és la pista amb què arrenca la resta del mòdul.
Contingut
- Què fa
docker runrealment:create+start - Anatomia de la comanda i l'ordre dels arguments
- Família identitat:
--namei--hostname - Família execució:
-d,-it,--rm,-w,-u,--entrypoint - Família xarxa i ports: el mínim per treballar
- Família configuració:
-ei--env-file - Família dades:
-ven la seva versió mínima - Sobreescriure el
CMDi l'ENTRYPOINTdes de la línia de comandes - Primer pla, segon pla i com desacoblar-se
docker attachenfront dedocker exec- Pràctica: la base de dades i la memòria cau d'Aurora Libros
- Què fa
docker run realment: create + start
docker run realment: create + startdocker run és una drecera. Per sota executa dues operacions diferents que pots invocar per separat:
Docker imprimeix l'ID complet del contenidor (64 caràcters hexadecimals) i retorna el control. Fixa't en el que ha passat i en el que no:
El contenidor existeix, té la seva capa d'escriptura reservada i tota la configuració desada (ports, variables, comanda), però l'estat és Created i la columna PORTS està buida: no hi ha cap procés en marxa i el port 8080 no escolta a la teva màquina. Comprova-ho:
Ara arrenca'l:
docker start imprimeix el nom del contenidor arrencat. I ara sí:
docker ps --filter name=prova-cicle --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080Aquesta separació no és un detall acadèmic: explica el repartiment de responsabilitats que faràs servir durant tot el mòdul.
| Fase | Què fa | Què es pot canviar després |
|---|---|---|
create |
Reserva la capa d'escriptura, desa la configuració (nom, ports, variables, muntatges, comanda, límits) | Gairebé res: ports, variables i muntatges queden fixats per sempre |
start |
Crea els namespaces, aplica els cgroups i llança el procés PID 1 | Es pot repetir tantes vegades com vulguis (stop/start) |
La conseqüència pràctica, que convé gravar-se ja: no pots afegir un port ni una variable d'entorn a un contenidor que ja existeix. Si t'has equivocat, s'esborra i es crea de nou. És exactament per això que al mòdul 4 acabaràs escrivint aquesta configuració en un fitxer en comptes de a la línia de comandes.
Neteja la demostració:
El flux complet, ara que coneixes les peces de la lliçó 01-03:
flowchart TD
A["docker run -d -p 8080:80 nginx:alpine"] --> B{"Hi és la imatge<br/>en local?"}
B -- No --> C["docker pull implícit<br/>des del registre"]
B -- Sí --> D["FASE CREATE"]
C --> D
D --> D1["Capa d'escriptura (copy-on-write)"]
D --> D2["Configuració: nom, ports,<br/>variables, comanda"]
D1 --> E["FASE START"]
D2 --> E
E --> E1["Namespaces: pid, net, mnt, uts, ipc"]
E --> E2["Cgroups: memòria i CPU"]
E --> E3["Regles de xarxa i publicació de ports"]
E1 --> F["runc llança el procés PID 1"]
E2 --> F
E3 --> F
F --> G["Contenidor en estat running"]
- Anatomia de la comanda i l'ordre dels arguments
Hi ha una sola regla d'or i provoca la meitat dels errors dels principiants:
Tot el que va abans de la imatge són opcions de Docker. Tot el que va després de la imatge és la comanda que s'executa dins del contenidor.
Mira-la fallar:
docker: Error response from daemon: failed to create task for container:
failed to create shim task: OCI runtime create failed: exec: "-e": executable file not found in $PATHDocker no ha interpretat -e com una opció seva: com que venia després d'alpine:3.20, ha intentat executar dins del contenidor un programa anomenat -e. La versió correcta:
Aquí --rm i -e MISSATGE=hola són opcions de Docker (van abans de la imatge) i sh -c 'echo $MISSATGE' és la comanda dins del contenidor (va després). Necessitem sh -c perquè l'expansió de $MISSATGE la fa un shell, i sense ell Docker passaria la cadena literal.
Una ullada general a les famílies que veuràs a les seccions següents:
| Família | Opcions | Per a què |
|---|---|---|
| Identitat | --name, --hostname, --label |
Poder referir-te al contenidor i organitzar-lo |
| Execució | -d, -it, --rm, -w, -u, --entrypoint |
Com i amb quina identitat corre el procés |
| Xarxa i ports | -p, -P, --expose, --network |
Qui pot parlar amb ell (a fons a 03-05) |
| Configuració | -e, --env-file |
Quins valors llegeix l'aplicació |
| Dades | -v, --mount, --tmpfs |
Què sobreviu al contenidor (a fons a 03-06) |
| Recursos i resiliència | -m, --cpus, --restart |
Quant pot consumir i què passa si falla (a fons a 03-07) |
- Família identitat:
--name i --hostname
--name i --hostname--name
Sense --name, Docker inventa un nom de dues paraules (nostalgic_hopper, elegant_bardeen) i t'obliga a copiar IDs. Amb nom, totes les comandes següents es tornen llegibles i automatitzables amb scripts:
Regles dels noms:
| Regla | Detall |
|---|---|
| Caràcters vàlids | [a-zA-Z0-9][a-zA-Z0-9_.-]* |
| Únic a tota la màquina | Inclosos els contenidors aturats |
| Serveix com a nom DNS | En una xarxa pròpia, un altre contenidor el resol per aquest nom (lliçó 03-05) |
Aquest darrer punt és la clau de tot el mòdul, i per això els noms d'Aurora Libros (aurora-db, aurora-cache, aurora-api, aurora-web) no són decoratius: d'aquí a poc seran noms de host reals.
L'error més habitual amb --name:
docker: Error response from daemon: Conflict. The container name "/aurora-cache" is already in use by container "a1b2c3d4e5f6".
You have to remove (or rename) that container to be able to reuse that name.Compte: el conflicte es produeix encara que el contenidor anterior estigui aturat. Un contenidor Exited continua ocupant el seu nom. Ho resols amb docker rm aurora-cache (o docker rm -f si està corrent).
--hostname
Canvia el nom que el contenidor veu de si mateix dins del seu propi namespace UTS:
Sense --hostname, el hostname és l'ID curt del contenidor:
--name i --hostname són coses diferents: --name és com l'anomenes tu des de fora (i com l'anomenen altres contenidors per DNS); --hostname és com s'anomena el contenidor a si mateix. Es fa servir poc, però apareix en registres d'aplicacions i en clústers de bases de dades.
- Família execució:
-d, -it, --rm, -w, -u, --entrypoint
-d, -it, --rm, -w, -u, --entrypoint| Opció | Què fa | Quan la fas servir |
|---|---|---|
-d, --detach |
Arrenca en segon pla i imprimeix l'ID | Serveis: bases de dades, APIs, servidors web |
-i, --interactive |
Manté l'entrada estàndard oberta | Quan escriuràs alguna cosa al procés |
-t, --tty |
Assigna un pseudo-terminal (indicador, colors, Ctrl+C) |
Shells interactives |
-it |
La combinació de les dues anteriors | Entrar en un contenidor amb sh o bash |
--rm |
Esborra el contenidor en acabar | Comandes d'un sol ús i proves |
-w, --workdir |
Directori de treball, sobreescriu el WORKDIR de la imatge |
Executar alguna cosa en una altra ruta sense reconstruir |
-u, --user |
Usuari/UID amb què corre el procés | Ajustar permisos o entrar com a root a una imatge sense privilegis |
--entrypoint |
Substitueix l'ENTRYPOINT de la imatge |
Depurar una imatge on l'executable fix fa nosa |
-d enfront de primer pla
Retorna l'ID i et deixa el terminal lliure. Sense -d, el terminal queda ocupat mostrant la sortida del procés i Ctrl+C l'atura.
-it i per què calen les dues lletres
Prova de treure una de les dues lletres per entendre què aporta cadascuna:
| Comanda | Resultat |
|---|---|
docker run --rm alpine:3.20 sh |
El shell arrenca, no troba entrada i acaba immediatament. Tornes a l'indicador del host |
docker run --rm -i alpine:3.20 sh |
Funciona: pots escriure comandes, però sense indicador, sense colors i sense historial |
docker run --rm -t alpine:3.20 sh |
Veus l'indicador / #, però el que escrius no hi arriba: el contenidor no té stdin. Queda penjat |
docker run --rm -it alpine:3.20 sh |
Sessió interactiva completa |
Regla pràctica: -it per a persones, -d per a serveis, res per a comandes d'un sol tret. I mai -it en un script automatitzat ni a CI: sense terminal real, -t provoca the input device is not a TTY.
--rm
El contenidor s'executa, imprimeix i desapareix: no queda a docker ps -a ni ocupa capa d'escriptura. És l'opció que evita acumular desenes de contenidors morts.
Dos avisos importants:
--rmés incompatible amb la depuració: si el contenidor falla, s'esborra juntament amb els seus registres i el seuinspect. Quan alguna cosa va malament, treu el--rmper poder investigar (lliçó 03-04).--rmesborra també els volums anònims associats. Amb volums amb nom no passa res, però és un detall que es reprèn a la lliçó 03-06.
-w i -u
docker run --rm -w /tmp alpine:3.20 pwd
docker run --rm -u 1000:1000 alpine:3.20 id
docker run --rm -u root auroralibros/aurora-api:1.1.0 id-w /tmpsobreescriu elWORKDIR /appque vas fixar al Dockerfile.-u 1000:1000força un UID:GID concret encara que aquest usuari no existeixi dins de la imatge (per aixòidmostra els números sense nom).-u rootanul·la elUSER nodede la teva imatge. És utilíssim per depurar (instal·lar una eina dins d'un contenidor sense privilegis), i alhora és un recordatori queUSERés una defensa en profunditat, no una barrera infranquejable: qui pot llançar contenidors pot triar l'usuari.
--entrypoint
Ja el vas fer servir a la lliçó 02-04 i aquí formalitza el seu lloc:
/app $ ls
Dockerfile node_modules package-lock.json package.json server.js
/app $ node --version
v22.13.0Sense --entrypoint, la teva imatge té ENTRYPOINT ["node"] fix i qualsevol cosa que escrivissis després de la imatge seria un argument de node. Amb --entrypoint sh hi entres a mirar. És la porta de servei de tota imatge ben construïda.
- Família xarxa i ports: el mínim per treballar
Aquí només l'imprescindible; el perquè complet arriba a la lliçó 03-05.
-p HOST:CONTENIDOR significa "tot el que arribi al port 8080 de la meva màquina, redirigeix-ho al port 80 d'aquest contenidor". L'ordre mai s'inverteix: primer el host, després el contenidor.
| Sintaxi | Significat |
|---|---|
-p 8080:80 |
Port 8080 de totes les interfícies del host → port 80 del contenidor |
-p 127.0.0.1:8080:80 |
Només des de la teva pròpia màquina; no visible a la xarxa local |
-p 80 |
Port 80 del contenidor a un port aleatori lliure del host |
-p 8080:80/udp |
Publica UDP en comptes de TCP |
-P |
Publica tots els ports declarats amb EXPOSE, cadascun en un port aleatori |
--expose 9000 |
Declara el port a les metadades, sense publicar-lo al host |
Comprova -P amb la teva pròpia imatge, que declara EXPOSE 3000:
Docker ha triat el 32768 del rang efímer. docker port és la comanda que respon a "en quin port del host ha quedat això?".
Dues idees que convé fixar des d'ara, encara que es desenvolupin a 03-05:
EXPOSEal Dockerfile i--exposearunno obren res: són documentació que consumeixen-Pi altres eines.- Publicar un port serveix perquè tu, des del host, arribis al contenidor. Perquè dos contenidors es parlin entre ells no cal publicar res. Aquesta distinció és la que està a punt de resoldre el misteri de l'API.
Neteja:
- Família configuració:
-e i --env-file
-e i --env-fileEl teu server.js no té ni un valor fix escrit: llegeix PORT, DB_HOST, DB_USER, DB_PASSWORD, DB_NAME i REDIS_HOST de l'entorn. Aquesta decisió de la lliçó 01-07 és la que permet que la mateixa imatge serveixi per al teu portàtil, per a proves i per a producció.
-e en les seves tres formes
docker run --rm -e SALUTACIO="Hola Aurora" alpine:3.20 printenv SALUTACIO
export TOKEN_LOCAL=abc123
docker run --rm -e TOKEN_LOCAL alpine:3.20 printenv TOKEN_LOCAL
docker run --rm auroralibros/aurora-api:1.1.0 --version| Forma | Efecte |
|---|---|
-e CLAU=valor |
Defineix la variable amb aquest valor literal |
-e CLAU |
Copia el valor que tingui aquesta variable al shell del host |
Sense -e |
L'aplicació fa servir l'ENV de la imatge o el seu valor per defecte al codi |
La segona forma és molt pràctica per no escriure secrets a la línia de comandes, on quedarien a l'historial del shell.
Les variables definides amb -e sobreescriuen les de l'ENV del Dockerfile. Comprova-ho amb la teva imatge, que porta ENV PORT=3000:
I torna a veure tota l'escala de prioritat:
| Prioritat | Origen | Exemple |
|---|---|---|
| 1 (guanya) | -e / --env-file a docker run |
-e PORT=4000 |
| 2 | ENV del Dockerfile |
ENV PORT=3000 |
| 3 | Valor per defecte al codi | process.env.PORT || 3000 |
--env-file
Quan passes de tres variables, la línia de comandes es torna il·legible. Crea ~/aurora-libros/aurora.env:
# Configuració local d'Aurora Libros — NO es cou dins la imatge
PORT=3000
DB_HOST=aurora-db
DB_PORT=5432
DB_USER=aurora
DB_PASSWORD=aurora_secreta
DB_NAME=aurora_llibres
REDIS_HOST=aurora-cache
REDIS_PORT=6379I fes-lo servir:
docker run --rm --env-file ~/aurora-libros/aurora.env \
--entrypoint printenv auroralibros/aurora-api:1.1.0 DB_HOST DB_NAMELes regles del format són estrictes i diferents de les d'un script de shell. Aquest és un punt on es perd molt de temps:
| Regla | Correcte | Incorrecte i per què |
|---|---|---|
Una variable per línia, CLAU=valor |
DB_USER=aurora |
export DB_USER=aurora → la clau seria export DB_USER |
| Sense cometes llevat que formin part del valor | DB_PASSWORD=aurora_secreta |
DB_PASSWORD="aurora_secreta" → la contrasenya inclouria les cometes |
| Sense expansió de variables | RUTA=/app/dades |
RUTA=$HOME/dades → el valor literal seria $HOME/dades |
Comentaris amb # a principi de línia |
# comentari |
DB_PORT=5432 # el port → el valor seria 5432 # el port |
Sense espais al voltant de l'= |
PORT=3000 |
PORT = 3000 → la clau seria PORT |
I una regla de seguretat que ja coneixes del mòdul 2, ara amb la seva conseqüència concreta:
echo "aurora.env" >> ~/aurora-libros/.gitignore
echo "aurora.env" >> ~/aurora-libros/api/.dockerignoreEl fitxer d'entorn conté una contrasenya: no va al repositori de Git ni al context de construcció. La imatge es queda sense secrets —com ha de ser— i la configuració viatja per fora, injectada a l'arrencada. Pots combinar les dues opcions: --env-file per al gruix i -e per al que vulguis sobreescriure puntualment, perquè -e guanya sobre --env-file independentment de l'ordre en què els escriguis.
- Família dades:
-v en la seva versió mínima
-v en la seva versió mínimaEl vas fer servir a la lliçó 01-06 per servir el teu index.html amb Nginx:
docker run -d --name aurora-web-demo -p 8080:80 \
-v ~/aurora-libros/web/index.html:/usr/share/nginx/html/index.html:ro \
nginx:alpineLa sintaxi mínima és -v ORIGEN:DESTINACIÓ[:opcions], amb la ruta del host sempre absoluta i :ro per muntar en només lectura. Amb això en tens prou fins a la lliçó 03-06, on veuràs els tres tipus de muntatge, els volums gestionats i per què el catàleg de llibres que estàs a punt de crear desapareixerà si no hi fas res. De moment, queda't amb la idea de la lliçó 01-05: tot el que un contenidor escriu fora d'un muntatge viu a la seva capa d'escriptura i mor amb ell.
- Sobreescriure el
CMD i l'ENTRYPOINT des de la línia de comandes
CMD i l'ENTRYPOINT des de la línia de comandesReprenem la taula de la lliçó 02-04, ara des del costat de l'execució. La teva imatge declara:
La comanda efectiva és la concatenació ENTRYPOINT + CMD → node server.js. I des de docker run pots tocar cada meitat per separat:
| Comanda | ENTRYPOINT |
CMD |
S'executa | Resultat |
|---|---|---|---|---|
docker run IMG |
node |
server.js |
node server.js |
Arrenca l'API |
docker run IMG --version |
node |
--version |
node --version |
Imprimeix v22.13.0 |
docker run IMG -e "console.log(2+2)" |
node |
-e console.log(2+2) |
node -e "console.log(2+2)" |
Imprimeix 4 |
docker run --entrypoint sh IMG |
sh |
(anul·lat) | sh |
Shell interactiva |
docker run --entrypoint sh IMG -c "ls /app" |
sh |
-c ls /app |
sh -c "ls /app" |
Llista el directori |
docker run --entrypoint "" IMG ls -la /app |
(cap) | ls -la /app |
ls -la /app |
Executa ls directament |
Comprova-ho:
docker run --rm auroralibros/aurora-api:1.1.0 -e "console.log('Aurora ' + (2+2))"
docker run --rm --entrypoint sh auroralibros/aurora-api:1.1.0 -c "ls /app | head -3"
docker run --rm --entrypoint "" auroralibros/aurora-api:1.1.0 ls -la /app/server.jsAurora 4
Dockerfile
node_modules
package-lock.json
-rw-r--r-- 1 node node 4187 Aug 4 19:12 /app/server.jsDos matisos que causen sorpreses:
- Sobreescriure l'
ENTRYPOINTdescarta elCMDde la imatge. A la fila 4 de la taula, elCMD ["server.js"]desapareix: si no vols queshrebiserver.jscom a argument no has de fer res, ja no hi és. --entrypoint ""(cadena buida) és la manera de deixar la imatge sense executable fix, de manera que el que escriguis darrere de la imatge sigui la comanda completa. És el truc que salva el dia amb imatges l'entrypoint de les quals és un script complicat.
- Primer pla, segon pla i com desacoblar-se
Quan arrenques sense -d, el teu terminal queda enganxat a l'entrada i la sortida del PID 1:
/docker-entrypoint.sh: Configuration complete; ready for start up
2026/08/04 19:41:07 [notice] 1#1: start worker processesAquí es queda. Si prems Ctrl+C, envies SIGINT al procés i el contenidor s'atura. Això rarament és el que vols amb un servei.
L'alternativa, si vas arrencar amb -it, és desacoblar-te sense matar-lo amb la seqüència Ctrl+P seguida de Ctrl+Q:
Continua viu. Tres condicions perquè la seqüència funcioni, i la seva absència explica el 90 % dels "a mi no em va":
- El contenidor ha d'haver-se arrencat amb
-t(necessita el pseudo-terminal). - Ha d'estar enganxat a stdin, és a dir, amb
-i. - La seqüència es prem dins de la sessió, no en un altre terminal.
Si necessites Ctrl+P per a una altra cosa (a bash recupera la comanda anterior), canvia-la:
Ara la combinació és Ctrl+E seguida de e. Ho pots fer permanent a ~/.docker/config.json:
Resum de les tres maneres d'arrencar:
| Forma | Comanda | Terminal | Ctrl+C |
|---|---|---|---|
| Primer pla | docker run IMG |
Ocupat mostrant la sortida | Atura el contenidor |
| Primer pla interactiu | docker run -it IMG sh |
Ocupat, amb sessió | Va al procés de dins |
| Segon pla | docker run -d IMG |
Lliure immediatament | No aplica |
Neteja:
docker attach enfront de docker exec
docker attach enfront de docker execUn contenidor en segon pla es pot "tornar a mirar" de dues maneres radicalment diferents:
docker attach et connecta a l'entrada i sortida del procés PID 1 que ja existeix. No llança res de nou. I aquí hi ha el seu perill:
Has aturat Redis. Ctrl+C durant un attach va directe al PID 1. Per sortir sense matar-lo cal fer servir Ctrl+P Ctrl+Q, o directament connectar-se en mode segur:
Amb --sig-proxy=false, Ctrl+C et retorna l'indicador sense enviar el senyal al contenidor.
docker exec, en canvi, llança un procés nou dins del contenidor:
Aquest redis-cli no té res a veure amb el PID 1: és un segon procés que comparteix els namespaces del contenidor i que pot acabar sense afectar el servei.
docker attach |
docker exec |
|
|---|---|---|
| Què fa | Es connecta al PID 1 existent | Crea un procés nou |
| Es pot fer servir diverses vegades alhora? | Sí, però tots veuen el mateix | Sí, independents |
Ctrl+C |
Mata el servei (llevat de --sig-proxy=false) |
Acaba només el teu procés |
| Sortir sense mal | Ctrl+P Ctrl+Q |
exit |
| Ús típic | Veure la sortida en directe d'un procés interactiu | Obrir un shell, executar diagnòstics |
| Recomanació | Evitar-lo llevat de casos concrets | L'opció per defecte |
La regla pràctica: per mirar la sortida fes servir docker logs i per entrar fes servir docker exec. Tots dos s'estudien a fons a la lliçó 03-04. docker attach es reserva per a processos que de debò esperen la teva entrada per teclat.
- Pràctica: la base de dades i la memòria cau d'Aurora Libros
És el moment. Fins ara, l'API arrencava sola i fallava perquè no hi havia ni PostgreSQL ni Redis. Els posarem.
La base de dades
docker run -d \
--name aurora-db \
-e POSTGRES_USER=aurora \
-e POSTGRES_PASSWORD=aurora_secreta \
-e POSTGRES_DB=aurora_llibres \
-p 127.0.0.1:5432:5432 \
postgres:16-alpineRepassem cada línia, perquè cadascuna té el seu motiu:
| Fragment | Per què |
|---|---|
-d |
És un servei: no volem el terminal ocupat |
--name aurora-db |
D'aquí a dues lliçons aquest nom serà el hostname que faci servir l'API |
-e POSTGRES_USER/PASSWORD/DB |
Són les tres variables que la imatge oficial de PostgreSQL fa servir per inicialitzar-se el primer cop. Coincideixen amb el que espera server.js |
-p 127.0.0.1:5432:5432 |
Publiquem només en local per poder connectar-hi amb un client des del host. Si escrivissis -p 5432:5432, la teva base de dades quedaria accessible des de tota la xarxa local |
postgres:16-alpine |
La mateixa imatge que vas descarregar al mòdul 2 |
Comprova que és viu:
docker ps --filter name=aurora-db --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
docker exec aurora-db pg_isready -U auroraNAMES IMAGE STATUS
aurora-db postgres:16-alpine Up 12 seconds
/var/run/postgresql:5432 - accepting connectionspg_isready és l'eina oficial de PostgreSQL per preguntar "acceptes connexions?". L'executem dins del contenidor amb docker exec, sense necessitat de tenir PostgreSQL instal·lat al host. Aquest detall —fer servir les eines que ja venen a la imatge— és una de les comoditats més infravalorades de Docker.
I comprova que la base de dades existeix, encara que de moment estigui buida:
Correcte: la base de dades aurora_llibres està creada, però no hi ha taules. Falta executar db/init.sql, i això es farà de manera automàtica i elegant a la lliçó 03-06, quan muntis aquest fitxer dins del contenidor.
La memòria cau
Redis no necessita cap variable: arrenca amb la seva configuració per defecte.
docker exec aurora-cache redis-cli PING
docker exec aurora-cache redis-cli SET prova "Aurora Libros"
docker exec aurora-cache redis-cli GET provaRedis funciona. Dos contenidors vius.
I ara l'API... que continua sense funcionar
docker run -d \
--name aurora-api \
-p 3000:3000 \
--env-file ~/aurora-libros/aurora.env \
auroralibros/aurora-api:1.1.0Docker ha acceptat la comanda sense protestar. Però:
El contenidor ni tan sols s'ha quedat en marxa. Ha arrencat i ha mort amb codi de sortida 1. Vegem per què:
[cache] error: getaddrinfo ENOTFOUND aurora-cache
[aurora-api] fallada en arrencar: getaddrinfo ENOTFOUND aurora-cacheFixa't bé en el missatge, perquè ha canviat respecte al mòdul 2 i aquest canvi és una pista de primer ordre:
| Configuració | Error | Què significa |
|---|---|---|
DB_HOST=localhost (mòdul 2) |
ECONNREFUSED 127.0.0.1:5432 |
El nom sí que es va resoldre (a si mateix), però ningú no escolta en aquell port dins del contenidor |
DB_HOST=aurora-db (ara) |
getaddrinfo ENOTFOUND aurora-db |
El nom ni tan sols s'ha pogut resoldre: per a aquest contenidor, aurora-db no existeix |
I aquí hi ha la paradoxa que dona sentit a la resta del mòdul: aurora-db i aurora-cache estan corrent ara mateix, a la mateixa màquina, publicant els seus ports. Ho pots comprovar des del host:
NAMES STATUS PORTS
aurora-cache Up 4 minutes 127.0.0.1:6379->6379/tcp
aurora-db Up 6 minutes 127.0.0.1:5432->5432/tcpSón vius, estan publicats, i tot i així aurora-api no els troba. Per què?
La resposta curta: perquè no comparteixen xarxa. Cada contenidor té la seva pròpia pila de xarxa, i a la xarxa per defecte de Docker no hi ha resolució de noms entre contenidors. La resposta llarga, amb la solució completa i el curl que retorna els vuit llibres, és la lliçó 03-05. De moment, deixa la incògnita plantejada i no esborris res: aquests dos contenidors t'acompanyen durant tot el mòdul.
Esborrem només l'API (està aturada i no serveix de res). aurora-db i aurora-cache es queden.
Errors Habituals i Consells
- Posar opcions després de la imatge.
docker run alpine -e VAR=1 shintenta executar un programa anomenat-e. Les opcions van abans de la imatge, sempre. - Esperar poder afegir un port o una variable a un contenidor existent. No es pot: es fixen a la fase
create. La solució ésdocker rmi tornar a crear. Quan això et cansi, ja estaràs a punt per al mòdul 4. - Fer servir
--rmmentre depures. Si el contenidor falla i s'esborra, et quedes sense registres i senseinspect. Durant la investigació, treu el--rm. -p 5432:5432en una base de dades. Publica PostgreSQL a totes les interfícies, inclosa la wifi del bar. Fes servir-p 127.0.0.1:5432:5432, o directament no el publiquis: els contenidors no necessiten ports publicats per parlar entre ells.- Invertir l'ordre a
-p.-p 80:8080amb Nginx no funciona i l'error és confús, perquè Docker publica alegrement el 80 del host cap a un 8080 on no escolta ningú. Primer el host, després el contenidor. -ten scripts i CI.the input device is not a TTY. En automatització fes servir-io res, mai-t.- Cometes en un
--env-file.DB_PASSWORD="aurora_secreta"desa la contrasenya amb les cometes incloses i provoca unpassword authentication faileddesconcertant. Sense cometes. - Consell: fes servir
--namesempre. Un contenidor sense nom és un contenidor que d'aquí a deu minuts buscaràs per ID en undocker ps -aamb vint línies. - Consell: escriu els
docker runllargs en diverses línies amb\al final, i desa'ls en un fitxer de notes. Els repetiràs moltes vegades, i a la lliçó 03-07 veuràs quant han crescut.
Exercicis
Exercici 1: demostra la separació entre create i start
Sense fer servir docker run en cap moment, crea un contenidor de nginx:alpine anomenat exercici-cicle publicat al port 8090 i amb la variable ENTORN=proves. Abans d'arrencar-lo, respon amb comandes: en quin estat està?, respon el port 8090?, apareix a docker ps? Després arrenca'l, comprova les tres coses de nou, i intenta afegir-li un segon port (8091) sense esborrar-lo. Explica què passa i per què.
Exercici 2: domina la sobreescriptura d'ENTRYPOINT i CMD
Fent servir exclusivament la imatge auroralibros/aurora-api:1.1.0 i sense reconstruir-la, aconsegueix aquestes cinc sortides, escrivint la comanda exacta en cada cas:
- La versió de Node instal·lada.
- El contingut de
/app/package.json. - Un shell interactiu dins del contenidor.
- El resultat de
console.log(process.env.DB_HOST)ambDB_HOSTvalentaurora-db. - La llista de fitxers de
/appexecutantlsdirectament, sense que hi intervinguinodenish.
Exercici 3: configuració per fitxer per a dos entorns
Crea dos fitxers d'entorn, aurora-dev.env i aurora-proves.env, que difereixin en PORT (3000 i 3100), DB_NAME (aurora_llibres i aurora_llibres_test) i REDIS_HOST. Arrenca dos contenidors de l'API amb la mateixa comanda llevat de l'--env-file i el --name, verifica amb printenv que cadascun té la seva configuració, i després arrenca un tercer que faci servir aurora-dev.env però amb PORT=3200 sobreescrit des de la línia de comandes. Respon: quantes imatges diferents has necessitat?, i quina línia del teu .gitignore impedeix que això acabi al repositori?
Solucions
Solució a l'exercici 1
Estat abans d'arrencar:
docker ps -a --filter name=exercici-cicle --format "table {{.Names}}\t{{.State}}\t{{.Ports}}"
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8090
docker ps --filter name=exercici-cicle -qLes tres respostes: estat created, el port no respon (codi 000, connexió rebutjada) i no apareix a docker ps (la sortida de la tercera comanda és buida), perquè docker ps sense -a només llista contenidors en execució. La configuració està desada, però no hi ha procés ni regla de xarxa.
docker start exercici-cicle
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8090
docker exec exercici-cicle printenv ENTORNAra sí: apareix a docker ps, el port retorna 200 i la variable que vas definir a la fase create hi és present.
L'intent d'afegir un port:
No hi ha cap manera de fer-ho. docker update només modifica recursos (memòria, CPU, política de reinici — lliçó 03-07), mai ports, variables ni muntatges: aquests es fixen en crear el contenidor perquè impliquen namespaces de xarxa i regles de reenviament que es construeixen a l'arrencada. L'única sortida és recrear:
docker rm -f exercici-cicle
docker run -d --name exercici-cicle -p 8090:80 -p 8091:80 -e ENTORN=proves nginx:alpine
docker rm -f exercici-cicleAquesta rigidesa és precisament l'argument a favor de declarar la configuració en un fitxer versionat en comptes de a la línia de comandes, que és el que fa Docker Compose al mòdul 4.
Solució a l'exercici 2
# 1. Versió de Node: el CMD "server.js" se substitueix per "--version",
# i l'ENTRYPOINT ["node"] continua al seu lloc → node --version
docker run --rm auroralibros/aurora-api:1.1.0 --version
# 2. Contingut de package.json: necessitem "cat", que no és node.
# Anul·lem l'entrypoint per complet.
docker run --rm --entrypoint cat auroralibros/aurora-api:1.1.0 /app/package.json
# 3. Shell interactiu: entrypoint sh + les dues lletres d'interactivitat
docker run --rm -it --entrypoint sh auroralibros/aurora-api:1.1.0
# 4. Avaluar codi amb una variable d'entorn: -e de Docker (abans de la
# imatge) i -e de node (després). El mateix guionet, dos significats.
docker run --rm -e DB_HOST=aurora-db auroralibros/aurora-api:1.1.0 \
-e "console.log(process.env.DB_HOST)"
# 5. Executar ls directament, sense node ni sh pel mig
docker run --rm --entrypoint "" auroralibros/aurora-api:1.1.0 ls -1 /appv22.13.0
{
"name": "aurora-api",
...
}
/app $
aurora-db
Dockerfile
node_modules
package-lock.json
package.json
server.jsEl cas 4 és el més instructiu de l'exercici: hi ha dos -e a la mateixa comanda i signifiquen coses diferents. El primer va abans de la imatge, així que és l'opció --env de Docker; el segon va després, així que és un argument que Docker lliura tal qual a l'ENTRYPOINT ["node"], i allà -e és l'opció de Node per avaluar codi. La regla de l'apartat 2 ho explica sense ambigüitat.
El cas 5 mostra la diferència entre --entrypoint ls i --entrypoint "". Amb --entrypoint ls hauries d'escriure els arguments després de la imatge igualment, però amb --entrypoint "" la línia es llegeix de manera natural: la imatge deixa d'imposar executable i tot el que segueix és la comanda completa.
Solució a l'exercici 3
cat > ~/aurora-libros/aurora-dev.env <<'EOF'
PORT=3000
DB_HOST=aurora-db
DB_USER=aurora
DB_PASSWORD=aurora_secreta
DB_NAME=aurora_llibres
REDIS_HOST=aurora-cache
EOF
cat > ~/aurora-libros/aurora-proves.env <<'EOF'
PORT=3100
DB_HOST=aurora-db
DB_USER=aurora
DB_PASSWORD=aurora_secreta
DB_NAME=aurora_llibres_test
REDIS_HOST=aurora-cache-test
EOFVerificació amb printenv, sense arrencar l'API (que fallaria pel que ja saps):
docker run --rm --env-file ~/aurora-libros/aurora-dev.env \
--entrypoint printenv auroralibros/aurora-api:1.1.0 PORT DB_NAME REDIS_HOST
docker run --rm --env-file ~/aurora-libros/aurora-proves.env \
--entrypoint printenv auroralibros/aurora-api:1.1.0 PORT DB_NAME REDIS_HOSTEl tercer, amb sobreescriptura puntual:
docker run --rm --env-file ~/aurora-libros/aurora-dev.env -e PORT=3200 \
--entrypoint printenv auroralibros/aurora-api:1.1.0 PORT DB_NAMEPORT val 3200 (ha guanyat el -e) i DB_NAME conserva el valor del fitxer. -e sempre té prioritat sobre --env-file, amb independència de l'ordre en què els escriguis a la línia de comandes: prova de posar-los a l'inrevés i obtindràs el mateix resultat.
Les dues respostes finals:
- Una sola imatge.
auroralibros/aurora-api:1.1.0ha servit per a tres configuracions diferents sense reconstruir-se ni una sola vegada. Aquest és el principi de construir una vegada, desplegar a tot arreu que vas veure a la lliçó 02-06: el que canvia entre entorns és la configuració injectada, mai la imatge. - La línia del
.gitignoreés*.env(oaurora-*.env). Aquests fitxers contenenDB_PASSWORD=aurora_secreta; al repositori no hi entren, i al.dockerignoretampoc, perquè no acabin al context de construcció ni, per accident, dins d'una capa de la imatge.
Conclusió
docker run ha deixat de ser una fórmula màgica. Saps que són dues operacions, create i start, i ho has demostrat executant-les per separat: la fase create reserva la capa d'escriptura i congela la configuració —per això no pots afegir un port ni una variable a un contenidor existent—, i la fase start munta namespaces, cgroups i regles de xarxa i llança el PID 1. Coneixes la regla d'or de l'ordre dels arguments, que separa les opcions de Docker de la comanda de dins, i tens les cinc famílies d'opcions ordenades: identitat, execució, xarxa, configuració i dades.
Manegues -d per a serveis, -it per a persones i --rm per a comandes d'un sol ús, sabent que aquest --rm és el teu enemic tan bon punt alguna cosa falla. Saps sobreescriure la meitat CMD i la meitat ENTRYPOINT d'una imatge per separat, inclosa l'anul·lació total amb --entrypoint "". Saps desacoblar-te d'una sessió amb Ctrl+P Ctrl+Q sense matar el procés, i per què docker attach és una eina esmolada que et pot tombar un servei amb un Ctrl+C distret, enfront de docker exec, que llança un procés independent. I saps portar la configuració fora de la imatge amb --env-file, amb les seves regles de format estrictes i la seva contrasenya que mai no entra ni a Git ni al context de construcció.
Sobretot, la plataforma ha començat a moure's: aurora-db i aurora-cache estan corrent ara mateix a la teva màquina, amb PostgreSQL 16 acceptant connexions i Redis responent PONG. I aurora-api ja no dona ECONNREFUSED: dona getaddrinfo ENOTFOUND aurora-db, un error diferent que diu una cosa molt concreta —el nom ni tan sols es resol— i que apunta directament a la solució. A més, el contenidor no es va quedar unhealthy: va morir en dos segons amb codi de sortida 1.
I això planteja la pregunta amb què continua el curs: per què uns contenidors es queden corrent indefinidament i altres moren a l'instant? A la lliçó següent, Cicle de Vida del Contenidor, veuràs els set estats pels quals passa un contenidor i les transicions entre ells, la regla que un contenidor viu exactament el que visqui el seu procés PID 1, la diferència entre una aturada ordenada amb SIGTERM i un SIGKILL als deu segons, i la taula de codis de sortida —aquest 1, i també el 125, el 137 i el 143— que converteix un número críptic en un diagnòstic. I ensenyaràs a server.js a apagar-se netament, tancant el pool de PostgreSQL i el client de Redis abans de marxar.
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
