Portes 44 lliçons construint imatges amb Docker, i hi ha una cosa que convé dir amb claredat: aquelles imatges no són «de Docker». Són artefactes que compleixen un estàndard obert, i funcionen igual a Podman, a containerd, a CRI-O o en un node de Kubernetes sense canviar ni un byte. Aquesta lliçó explica per què, desmunta l'ecosistema peça a peça i executa ghcr.io/auroralibros/aurora-api:2.0.0 fora de Docker per demostrar-ho.

Contingut

  1. La pregunta correcta: de qui és una imatge?
  2. L'estàndard OCI i les seves tres especificacions
  3. Què significa això a la pràctica
  4. El mapa de peces: runtimes de baix i d'alt nivell
  5. CRI: per què Kubernetes no parla amb Docker
  6. Podman: l'arquitectura sense daemon
  7. Rootless per defecte
  8. Compatibilitat de comandes i què es trenca
  9. Pods, generate kube i play kube
  10. Compose, el socket compatible i Testcontainers
  11. Docker davant de Podman, i la migració d'Aurora Libros
  12. containerd i nerdctl
  13. Buildah i Skopeo
  14. Aïllament reforçat: gVisor i Kata Containers
  15. Taula de decisió

  1. La pregunta correcta: de qui és una imatge?

Quan vas executar docker build -t aurora-api:2.0.0 ., Docker no va inventar cap format propi. Va produir un artefacte amb una estructura pública: un manifest JSON, una configuració amb les variables d'entorn, l'ENTRYPOINT, l'usuari i l'historial, i una llista de capes comprimides, tot identificat per digests SHA-256.

docker buildx imagetools inspect ghcr.io/auroralibros/aurora-api:2.0.0 --raw | jq '.'
# {
#   "mediaType": "application/vnd.oci.image.index.v1+json",
#   "manifests": [
#     { "platform": { "architecture": "amd64", "os": "linux" }, "digest": "sha256:6b1..." },
#     { "platform": { "architecture": "arm64", "os": "linux" }, "digest": "sha256:c47..." }
#   ]
# }

Fixa't en el mediaType: hi diu vnd.oci.image.index.v1+json, no «docker». Aquell prefix és la prova literal que la teva imatge és un artefacte OCI, i que la multiarquitectura que vas muntar a 05-05 és una funció de l'estàndard, no de Docker.

  1. L'estàndard OCI i les seves tres especificacions

L'Open Container Initiative va néixer el 2015 dins de la Linux Foundation, impulsada per la mateixa Docker, que hi va donar el seu format d'imatge i el seu runtime runc. El motiu va ser evitar una guerra de formats incompatibles (hi havia un competidor seriós, rkt, amb format propi). El resultat és que avui el contenidor és un estàndard i no un producte.

Especificació Què normalitza On l'has vista al curs
Image Spec Format de la imatge: manifest, config, capes, digests, índexs multiarquitectura Cada docker build i cada docker inspect
Runtime Spec Com s'executa un bundle: el config.json amb namespaces, cgroups, capabilities i muntatges El config.json de runc que vas obrir a 05-07
Distribution Spec El protocol HTTP dels registres d'imatges: push, pull, tags, digests, autenticació Cada docker push a ghcr.io (02-06)
graph TB
    subgraph oci["Estàndard OCI"]
        IS["Image Spec<br/>format de la imatge"]
        RS["Runtime Spec<br/>config.json i execució"]
        DS["Distribution Spec<br/>protocol del registre"]
    end
    subgraph constructors["Construeixen (Image Spec)"]
        BK["BuildKit"]; BAH["Buildah"]; KANIKO["Kaniko"]; PACK["Buildpacks"]
    end
    subgraph registres["Emmagatzemen (Distribution Spec)"]
        GHCR["ghcr.io"]; HARBOR["Harbor"]; ZOT["Zot"]; REG["registry:2"]
    end
    subgraph executors["Executen (Runtime Spec)"]
        DOCKER["Docker Engine"]; PODMAN["Podman"]; CTD["containerd"]; CRIO["CRI-O"]
    end
    constructors --> IS --> registres
    registres --> DS
    IS --> executors
    executors --> RS

Qualsevol constructor produeix imatges que qualsevol registre emmagatzema i qualsevol runtime executa. Aquella quadrícula completa és el que compra l'estàndard.

  1. Què significa això a la pràctica

Afirmació Certa?
«Necessito Docker per executar una imatge de Docker» Falsa: l'executa qualsevol runtime OCI
«Kubernetes executa imatges de Docker» Imprecisa: executa imatges OCI, i no fa servir Docker des del 2022
«Si canvio de runtime, he de reconstruir» Falsa: el mateix digest serveix
«Docker Hub només serveix per a Docker» Falsa: implementa la Distribution Spec
«Cosign i els SBOM són cosa de Docker» Falsa: són artefactes OCI al registre d'imatges

Conseqüència directa per a Aurora Libros: ghcr.io/auroralibros/aurora-api:2.0.0 —104 MB, dues arquitectures, signada amb Cosign, amb SBOM i attestation de procedència— corre exactament igual a Docker, a Podman, a containerd o en qualsevol node de Kubernetes, i la seva signatura es verifica igual a tots. No has après un producte: has après un estàndard i una de les seves implementacions.

  1. El mapa de peces: runtimes de baix i d'alt nivell

graph TB
    U["Tu: docker / podman / nerdctl / kubectl"]
    subgraph alt["Runtimes d'ALT nivell (gestors)"]
        DE["Docker Engine"]; PM["Podman"]; CD["containerd"]; CO["CRI-O"]
    end
    subgraph baix["Runtimes de BAIX nivell (OCI Runtime Spec)"]
        RC["runc (C/Go)"]; CR["crun (C)"]; YK["youki (Rust)"]
        GV["gVisor"]; KT["Kata Containers"]
    end
    K["Kernel de Linux: namespaces, cgroups, seccomp, capabilities"]
    U --> alt
    DE --> CD
    alt --> baix --> K
Capa Què fa Peces
Baix nivell Rep un bundle + config.json i crea el procés aïllat. Viu mil·lisegons runc, crun, youki, gVisor, Kata
Alt nivell Descarrega imatges, gestiona capes, xarxes, volums i cicle de vida; crida el de baix nivell containerd, CRI-O, Podman, Docker Engine
Interfície El que teclejes o l'API que consumeix Kubernetes docker, podman, nerdctl, ctr, CRI
Runtime de baix nivell Llenguatge Tret
runc Go La implementació de referència; la que fas servir
crun C Més ràpid i lleuger; menys memòria. Per defecte a Podman a Fedora
youki Rust Seguretat de memòria per disseny; jove però funcional
gVisor (runsc) Go Kernel en espai d'usuari: aïllament fort, cost alt
Kata Go Una microVM per contenidor: aïllament de VM

Canviar el de baix nivell és una línia de configuració, i les imatges no se n'assabenten:

// /etc/docker/daemon.json
{ "default-runtime": "runc",
  "runtimes": { "crun": { "path": "/usr/bin/crun" } } }
docker run --rm --runtime=crun alpine echo "executat amb crun"

  1. CRI: per què Kubernetes no parla amb Docker

Kubernetes necessita parlar amb algun runtime a cada node. En comptes d'acoblar-se a un, va definir la Container Runtime Interface, una API gRPC amb dos serveis: RuntimeService (cicle de vida de Pods i contenidors) i ImageService (imatges).

Docker Engine no va implementar mai CRI, així que va existir un adaptador anomenat dockershim dins del mateix kubelet. Era codi de Kubernetes mantenint compatibilitat amb un producte concret, i es va retirar a la versió 1.24 (2022).

graph LR
    subgraph abans["Abans de 1.24"]
        KL1["kubelet"] --> DS["dockershim"] --> DE["Docker Engine"] --> CD1["containerd"] --> RC1["runc"]
    end
    subgraph ara["Des de 1.24"]
        KL2["kubelet"] -->|CRI| CD2["containerd o CRI-O"] --> RC2["runc"]
    end

Allò es va comunicar fatal i va generar titulars del tipus «Kubernetes deixa de donar suport a Docker». El que realment va passar va ser que es va eliminar un intermediari: containerd, que ja hi era a sota, va passar a parlar directament amb el kubelet. Les teves imatges no se'n van veure afectades gens ni mica, perquè són OCI. És la millor il·lustració possible de la tesi d'aquesta lliçó.

  1. Podman: l'arquitectura sense daemon

Podman (Red Hat) és l'alternativa més rellevant a Docker, i la seva diferència estructural és que no hi ha daemon.

graph TB
    subgraph docker["Docker"]
        DC["client docker"] -->|"socket, root"| DD["dockerd (root, sempre viu)"]
        DD --> C1["contenidor"]
    end
    subgraph podman["Podman"]
        PC["podman (el teu usuari)"] --> CN["conmon"] --> C2["contenidor"]
        SD["systemd --user"] -.->|"supervisa"| C2
    end
Conseqüència Docker Podman
Procés sempre en marxa dockerd com a root Cap
Amo dels contenidors El daemon El teu usuari
Si el gestor mor Risc per als contenidors Continuen vius: els supervisa conmon
Superfície d'atac Un socket = root a l'host (05-03) No hi ha socket privilegiat
Arrencada a l'inici restart: gestionat pel daemon Unitats de systemd generades
Auditoria del sistema Tot apareix com a acció del daemon Apareix com a acció del teu usuari

La quarta fila és la raó de fons. El «el grup docker és equivalent a root» que vas aprendre a 05-03 simplement no existeix a Podman: no hi ha cap socket privilegiat al qual pertànyer. I l'última importa en entorns regulats: els registres d'auditoria atribueixen les accions a persones, no a un dimoni.

  1. Rootless per defecte

Docker admet el mode rootless des de la 20.10, però s'ha d'activar. A Podman és el normal des del principi, recolzat en user namespaces (05-07) i en els rangs de /etc/subuid i /etc/subgid.

podman run -d --name aurora-cache redis:7-alpine
podman top aurora-cache huser user
# HUSER       USER
# 100998      redis      ← UID 999 a dins; UID 100998 a l'host
id -u
# 1000                   ← i el procés pertany al teu usuari

El contenidor es pensa que és l'usuari redis (UID 999) i el kernel el veu com el 100998, un UID sense cap privilegi. Una fuga del contenidor no dona root: dona un usuari que no pot fer res.

Limitació del mode rootless Solució
No es poden publicar ports < 1024 sysctl net.ipv4.ip_unprivileged_port_start=80 o proxy al davant
El rendiment de xarxa passa per slirp4netns/pasta pasta (per defecte en versions recents) el millora molt
Alguns sistemes de fitxers no funcionen igual Fer servir fuse-overlayfs
Muntar dispositius o canviar sysctl de l'host Requereix privilegis: replantejar el disseny

  1. Compatibilitat de comandes i què es trenca

Podman replica la CLI de Docker de manera deliberada:

alias docker=podman
docker run -d -p 8080:8080 ghcr.io/auroralibros/aurora-api:2.0.0
docker ps ; docker logs ; docker exec ; docker build ; docker inspect   # tot igual

Funciona per a la immensa majoria de la feina diària. El que no és idèntic:

Diferència Detall
docker.io implícit Podman pregunta el registre o fa servir registries.conf; escriu sempre el nom complet
Magatzem d'imatges Separat per usuari a ~/.local/share/containers; el que construeixis com a root no ho veu el teu usuari
--privileged Menys privilegis reals en rootless: continues limitat pel teu usuari
Xarxes netavark en lloc del bridge de Docker; el DNS intern funciona igual però es configura diferent
restart: always No hi ha daemon que reiniciï: es genera una unitat de systemd
Swarm No existeix; el camí és Kubernetes
Docker Desktop L'equivalent és Podman Desktop

  1. Pods, generate kube i play kube

Podman pren manllevada de Kubernetes la seva unitat d'agrupació: el pod, un conjunt de contenidors que comparteixen namespace de xarxa (i per tant localhost i els ports).

podman pod create --name aurora --publish 8080:8080
podman run -d --pod aurora --name aurora-cache redis:7-alpine
podman run -d --pod aurora --name aurora-api \
  -e REDIS_URL=redis://localhost:6379 \
  ghcr.io/auroralibros/aurora-api:2.0.0
podman pod ps
# POD ID   NAME     STATUS    INFRA ID   # OF CONTAINERS
# 4f2a...  aurora   Running   9c1b...    3

Fixa't en REDIS_URL=redis://localhost:6379: dins d'un pod, els contenidors comparteixen la interfície de xarxa, així que s'arriben per localhost. És exactament el model d'un Pod de Kubernetes, assajat al teu portàtil.

I d'aquí en surt el pont més útil de Podman:

podman generate kube aurora > aurora-pod.yaml   # de contenidors a manifest K8s
podman play kube aurora-pod.yaml                # i de manifest K8s a contenidors
kubectl apply -f aurora-pod.yaml                # el mateix fitxer, al clúster

generate kube produeix un Pod (o Deployment amb --type deployment) llest per revisar. No substitueix la feina del mòdul 6 —no genera Ingress, HPA, PDB ni sondes afinades— però com a esborrany és molt millor que kompose, perquè parteix d'alguna cosa que ja funciona.

  1. Compose, el socket compatible i Testcontainers

Dos camins per executar el teu compose.yaml:

# Opció A: podman-compose (implementació en Python, cobertura parcial)
podman-compose -f compose.yaml up -d

# Opció B (recomanada): el socket compatible amb l'API de Docker
systemctl --user enable --now podman.socket
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock"
docker compose up -d              # el docker compose de debò, contra Podman

L'opció B és superior perquè fa servir el Compose oficial v2, amb tota la seva cobertura de claus, parlant amb Podman a través d'un socket que emula l'API de Docker. I aquell mateix socket habilita la resta:

# Testcontainers (07-04) funcionant contra Podman
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock"
export TESTCONTAINERS_RYUK_DISABLED=true    # Ryuk necessita ajustos en rootless
node --test test/                            # PostgreSQL 16 i Redis 7 reals
Eina Funciona contra Podman? Nota
docker compose v2 , via socket La millor opció
podman-compose Parcialment Claus avançades no cobertes
Testcontainers Desactivar Ryuk o configurar-lo
Trivy, dive, hadolint Llegeixen imatges pel socket o del registre
Portainer Sí, amb matisos Apunta al socket de Podman

  1. Docker davant de Podman, i la migració d'Aurora Libros

Aspecte Docker Podman
Arquitectura Client + daemon Sense daemon, fork/exec
Root per defecte Sí (rootless opcional) Rootless per defecte
Grup amb poder de root Sí: el grup docker No existeix
Compose Natiu, v2, complet Via socket (recomanat) o podman-compose
Orquestració pròpia Swarm Cap: apunta a Kubernetes
Pods No , a l'estil Kubernetes
Pont cap a K8s kompose (limitat) generate kube / play kube
Construcció BuildKit (excel·lent) Buildah (integrat, sense daemon)
Arrencada automàtica restart: Unitats de systemd
Windows i macOS Docker Desktop podman machine / Podman Desktop
Ecosistema i documentació Enorme Bo, menor
Per defecte a Gairebé a tot arreu RHEL, Fedora, CentOS Stream

Migració d'Aurora Libros, pas a pas i amb la comprovació al final:

# 1. La mateixa imatge, sense reconstruir res
podman pull ghcr.io/auroralibros/aurora-api:2.0.0
podman image inspect ghcr.io/auroralibros/aurora-api:2.0.0 \
  --format '{{.Digest}}  {{.Architecture}}'
# sha256:c47e...  arm64      ← digest idèntic al que verifica Cosign

# 2. La signatura es verifica igual: és un artefacte OCI al registre
cosign verify ghcr.io/auroralibros/aurora-api:2.0.0 \
  --certificate-identity-regexp 'auroralibros' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

# 3. La pila sencera, amb el compose.yaml sense tocar-hi una línia
systemctl --user enable --now podman.socket
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock"
docker compose up -d --wait

# 4. La prova que funciona de debò
curl -s localhost:8080/llibres | jq -r '.llibres[] | .titol' | head -3
# El jardín de senderos que se bifurcan
# Rayuela
# Cien años de soledad
curl -s localhost:8080/llibres/4 | jq -r '.origen'   # db
curl -s localhost:8080/llibres/4 | jq -r '.origen'   # cache  ← cache-aside intacte

# 5. Que sobrevisquin al reinici, amb systemd en lloc de restart:
podman generate systemd --new --files --name aurora-api
systemctl --user enable --now container-aurora-api.service
loginctl enable-linger $USER    # que arrenquin sense iniciar sessió

Zero canvis a la imatge, zero canvis al compose.yaml, mateixa signatura verificable i el cache-aside retornant db i després cache. L'única diferència real d'operació és al pas 5: on Docker tenia restart: unless-stopped, aquí hi ha unitats de systemd d'usuari, i l'enable-linger és el detall que s'oblida sempre.

  1. containerd i nerdctl

containerd és un projecte graduat de la CNCF, i és la peça que ja fas servir sense saber-ho: Docker Engine hi delega. Gestiona imatges, snapshots, xarxes bàsiques i el cicle de vida, i crida runc. És també el runtime per defecte de la majoria dels Kubernetes gestionats.

La seva CLI nativa, ctr, és una eina de depuració, no de treball:

sudo ctr images pull ghcr.io/auroralibros/aurora-api:2.0.0
sudo ctr run --rm ghcr.io/auroralibros/aurora-api:2.0.0 prova
# Sense xarxa còmoda, sense volums, sense publicació de ports: és de baix nivell

nerdctl és el que fa containerd usable: replica la CLI de Docker, inclou BuildKit i porta Compose.

nerdctl run -d -p 8080:8080 ghcr.io/auroralibros/aurora-api:2.0.0
nerdctl compose -f compose.yaml up -d       # sí: Compose sobre containerd
nerdctl build -t aurora-api:2.1.0 .          # BuildKit per sota
Eina Nivell Per a què
ctr Molt baix Depurar containerd; mai per al dia a dia
crictl CRI Depurar nodes de Kubernetes: veure Pods des del node
nerdctl Alt Treballar amb containerd com si fos Docker

nerdctl a més exposa funcions que Docker no té: imatges xifrades, lazy-pulling amb stargz (arrencada sense descarregar la imatge sencera) i contenidors Wasm. És la via habitual per experimentar amb el que ve.

  1. Buildah i Skopeo

Buildah construeix imatges OCI sense daemon i sense Dockerfile si no en vols:

buildah bud -t aurora-api:2.0.0 .        # fa servir el teu Dockerfile tal qual
# o de manera imperativa, útil en scripts:
ctr=$(buildah from node:22-alpine)
buildah copy   "$ctr" src /app/src
buildah config --user 10001 --port 8080 --entrypoint '["node","/app/src/index.js"]' "$ctr"
buildah commit "$ctr" aurora-api:2.0.0

Skopeo és el que més s'enyora quan no es coneix: inspecciona i copia imatges entre registres sense descarregar-les al disc local.

# Inspeccionar una imatge remota sense fer pull
skopeo inspect docker://ghcr.io/auroralibros/aurora-api:2.0.0 \
  | jq '{Digest, Architecture, Created, Labels}'

# Copiar entre registres directament (no passa pel teu disc)
skopeo copy --all \
  docker://ghcr.io/auroralibros/aurora-api:2.0.0 \
  docker://registre.intern:5000/aurora/aurora-api:2.0.0

# Comprovar si l'etiqueta de producció ha canviat, sense descarregar 104 MB
skopeo inspect --format '{{.Digest}}' docker://ghcr.io/auroralibros/aurora-api:2.0.0

L'--all copia l'índex multiarquitectura complet, no només la variant de la teva màquina: sense ell, replicaries mitja imatge i el servidor amd64 fallaria. I l'última comanda és or pur en un pipeline: verificar el digest de producció costa una petició HTTP en lloc d'una descàrrega completa.

  1. Aïllament reforçat: gVisor i Kata Containers

Els contenidors comparteixen el kernel de l'host. Si apareix una vulnerabilitat d'escalada al kernel, l'aïllament es trenca. Aquests dos runtimes ataquen justament això.

gVisor (runsc) Kata Containers
Com aïlla Kernel reimplementat en espai d'usuari que intercepta syscalls Una microVM amb kernel propi per contenidor
Aïllament Alt Molt alt (frontera d'hipervisor)
Sobrecost d'arrencada ~50-150 ms ~100-500 ms
Sobrecost d'E/S Notable Moderat
Compatibilitat Algunes syscalls no suportades Total: és Linux de debò
Requereix virtualització No Sí (imbricada al núvol)
Ús típic Executar codi no fiable Multitinença dura, compliment normatiu
docker run --rm --runtime=runsc alpine dmesg | head -1
# Starting gVisor...          ← no és el kernel de l'host

Per a Aurora Libros no calen: el teu codi és teu i ja corre sense privilegis, amb read_only, cap_drop: [ALL] i no-new-privileges. Es justifiquen quan executes codi que no controles —funcions de clients, CI de repositoris públics, quaderns de tercers— i allà el cost de rendiment es paga sense discussió.

  1. Taula de decisió

Situació Elecció natural Per què
Desenvolupament en equip, ecosistema ampli Docker Documentació, Desktop, Compose natiu, tothom el coneix
RHEL, Fedora o CentOS Stream Podman És l'estàndard del sistema, integrat amb systemd
Política de «res com a root» Podman Rootless per defecte, sense grup amb poder de root
Node de Kubernetes containerd o CRI-O Parlen CRI directament
Treballar amb containerd a mà nerdctl CLI de Docker sobre containerd, amb BuildKit
Construir a CI sense daemon Buildah, Kaniko o BuildKit Sense privilegis ni socket exposat
Copiar o auditar imatges entre registres Skopeo Sense descarregar res
Executar codi no fiable gVisor o Kata Aïllament reforçat
Compose, Swarm o Docker Desktop Docker Podman no té equivalent ple

La conclusió honesta: el 2026 Docker continua sent l'elecció natural per a desenvolupament per ecosistema i comoditat, Podman guanya en entorns on la seguretat i la política del sistema manen, i containerd és el que executa el món en producció sense que gairebé ningú no el teclegi. I les tres coses executen la teva mateixa imatge.

Errors Habituals i Consells

  • Creure que cal reconstruir per canviar de runtime. No. El digest és el mateix i la signatura de Cosign es verifica igual.
  • Repetir que «Kubernetes va deixar de donar suport a Docker». El que es va retirar va ser dockershim, un adaptador. Les imatges OCI mai no van estar en qüestió.
  • Barrejar contenidors de root i d'usuari a Podman. Els magatzems estan separats: el que construeixes amb sudo podman no ho veu podman. És la confusió número u en començar.
  • Esperar restart: always a Podman. No hi ha daemon. Genera unitats amb podman generate systemd i no oblidis loginctl enable-linger.
  • Publicar el port 80 en rootless sense ajustar res. Els ports privilegiats estan vetats. Ajusta ip_unprivileged_port_start o posa un proxy al davant.
  • Copiar imatges amb skopeo copy sense --all. T'endús només una arquitectura i l'altre entorn falla en desplegar.
  • Fer servir ctr per al dia a dia. És una eina de depuració de containerd. Fes servir nerdctl.
  • Consell: executa docker buildx imagetools inspect --raw sobre la teva imatge i llegeix el mediaType. Veure vnd.oci a la pantalla fixa la idea millor que qualsevol explicació.
  • Consell: si t'interessa Podman, comença pel socket compatible i docker compose. Migres l'execució sense migrar les eines.
  • Consell: podman generate kube és el millor esborrany de manifestos que existeix, perquè parteix d'alguna cosa que ja funciona.

Exercicis

Exercici 1 — Demostra la portabilitat. Executa ghcr.io/auroralibros/aurora-api:2.0.0 amb Docker i amb Podman (o nerdctl) a la mateixa màquina. Compara el digest de la imatge en tots dos, comprova que l'endpoint /llibres retorna els nou títols en els dos casos i verifica la signatura de Cosign contra el registre. Escriu què és idèntic i què canvia entre totes dues execucions.

Exercici 2 — Un pod a l'estil Kubernetes amb Podman. Crea un pod aurora que contingui aurora-cache (redis:7-alpine) i aurora-api, comunicant-se per localhost, amb el port 8080 publicat. Comprova que el cache-aside funciona (origen: db la primera vegada, cache la segona). Després genera el manifest de Kubernetes amb podman generate kube, llegeix-lo i anota tres coses que li falten per ser apte per a producció segons el que has après al mòdul 6.

Exercici 3 — Skopeo al pipeline. Escriu un script que, sense descarregar cap imatge, comprovi si el digest de ghcr.io/auroralibros/aurora-api:2.0.0 coincideix amb el que hi ha desplegat en producció (llegeix-lo d'un fitxer digest-produccio.txt), i que en cas de discrepància repliqui la imatge completa, amb les seves dues arquitectures, al registre intern registre.intern:5000. L'script ha de retornar 0 si tot coincideix i 10 si ha hagut de replicar.

Solucions

Solució 1.

IMG=ghcr.io/auroralibros/aurora-api:2.0.0

docker pull "$IMG" && podman pull "$IMG"
docker image inspect "$IMG" --format 'docker: {{index .RepoDigests 0}}'
podman image inspect "$IMG" --format 'podman: {{index .RepoDigests 0}}'
# docker: ghcr.io/auroralibros/aurora-api@sha256:c47e...
# podman: ghcr.io/auroralibros/aurora-api@sha256:c47e...   ← idèntic

docker run -d --name api-d -p 8080:8080 "$IMG"
podman run -d --name api-p -p 8081:8080 "$IMG"
curl -s localhost:8080/llibres | jq '.llibres | length'   # 9
curl -s localhost:8081/llibres | jq '.llibres | length'   # 9

cosign verify "$IMG" \
  --certificate-identity-regexp 'auroralibros' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Idèntic: el digest, el contingut, el comportament de l'API i la verificació de la signatura —perquè Cosign valida un artefacte que viu al registre, no al runtime—. Canvia el que envolta l'execució: a Docker el contenidor és fill de dockerd (root) i apareix a ps aux com a tal; a Podman rootless és fill de conmon sota el teu UID, el procés intern es mapeja a un UID alt de l'host, i podman ps només mostra els teus contenidors. També canvia la xarxa (netavark davant del bridge de Docker) i la gestió de l'arrencada automàtica. Cap d'aquestes diferències no afecta l'artefacte: el que s'executa és exactament el mateix.

Solució 2.

podman pod create --name aurora --publish 8080:8080
podman run -d --pod aurora --name aurora-cache redis:7-alpine
podman run -d --pod aurora --name aurora-api \
  -e REDIS_URL=redis://localhost:6379 \
  -e DB_HOST=host.containers.internal -e DB_NAME=aurora_llibres \
  ghcr.io/auroralibros/aurora-api:2.0.0

curl -s localhost:8080/llibres/6 | jq -r '.titol, .origen'
# La casa de los espíritus
# db
curl -s localhost:8080/llibres/6 | jq -r '.origen'
# cache

podman generate kube aurora > aurora-pod.yaml

El que li falta al manifest generat per ser apte per a producció: (1) les tres sondes —genera com a molt el que va deduir del contenidor, no les startupProbe, livenessProbe i readinessProbe sobre /salut/viu i /salut/preparat que vas separar a 06-01—; (2) resources amb requests i limits, sense els quals el planificador no pot col·locar el Pod amb criteri ni l'HPA calcular el percentatge d'ús; (3) és un Pod solt, no un Deployment, així que no hi ha rèpliques, ni rolling updates, ni rollback, ni recuperació si el node cau. N'hi afegiria dues més: no hi ha ConfigMap/Secret (la configuració va com a variables literals, incloses les credencials) i no hi ha Service ni Ingress. Serveix com a esborrany excel·lent perquè parteix d'alguna cosa que funciona, i com a recordatori que tot el que fa que un desplegament sigui apte per a producció és feina deliberada.

Solució 3.

#!/usr/bin/env bash
# sincronitzar-registre.sh — replica només si el digest ha canviat. Sense descarregar res.
set -euo pipefail
ORIGEN="docker://ghcr.io/auroralibros/aurora-api:2.0.0"
DESTI="docker://registre.intern:5000/aurora/aurora-api:2.0.0"
FITXER="digest-produccio.txt"

REMOT=$(skopeo inspect --format '{{.Digest}}' "$ORIGEN")
ACTUAL=$(cat "$FITXER" 2>/dev/null || echo "cap")

if [ "$REMOT" = "$ACTUAL" ]; then
  echo "Sense canvis: $REMOT"
  exit 0
fi

echo "Digest nou detectat"
echo "  producció: $ACTUAL"
echo "  registre:  $REMOT"
skopeo copy --all "$ORIGEN" "$DESTI"       # --all = les dues arquitectures
skopeo inspect --raw "$DESTI" | jq -r '.manifests[].platform.architecture'
# amd64
# arm64
echo "$REMOT" > "$FITXER"
exit 10

Les claus. skopeo inspect --format '{{.Digest}}' fa una única petició HTTP al manifest: comprovar si alguna cosa ha canviat costa mil·lisegons en lloc de descarregar 104 MB, i això permet executar-ho cada cinc minuts sense cost. skopeo copy transfereix de registre a registre sense materialitzar capes al disc local, cosa que un docker pull seguit d'un docker push no pot fer. L'--all és imprescindible: sense ell copiaries només el manifest que correspon a l'arquitectura de l'executor, i el dia que un node arm64 tirés de la còpia interna, el pull fallaria amb un error de plataforma que costa força diagnosticar; la verificació posterior amb --raw confirma que les dues arquitectures hi han arribat. I el codi de sortida 10 distingeix «no hi havia res a fer» d'«he replicat», que és el que permet encadenar un pas de notificació al pipeline només quan hi ha hagut canvi real.

Conclusió

Ja saps per què les imatges que has construït durant tot el curs no són «de Docker». Ho has comprovat llegint el mediaType del teu propi índex multiarquitectura: application/vnd.oci.image.index.v1+json. L'Open Container Initiative normalitza tres coses —la Image Spec que dona forma a les teves imatges, la Runtime Spec que defineix el config.json que vas obrir a 05-07, i la Distribution Spec que regeix cada push a ghcr.io—, i d'aquí en surt la quadrícula completa: qualsevol constructor, qualsevol registre, qualsevol runtime.

Tens el mapa de peces ordenat per capes: runtimes de baix nivell que viuen mil·lisegons (runc, crun, youki) davant de gestors d'alt nivell (containerd, CRI-O, Podman, Docker Engine), amb CRI al mig explicant per què el kubelet parla amb containerd i per què la retirada de dockershim a la 1.24 va ser eliminar un intermediari, no deixar de donar suport a les teves imatges.

Coneixes Podman a fons: sense daemon, amb els contenidors com a fills del teu usuari i no d'un procés root, cosa que fa que el «grup docker és root» de 05-03 simplement no existeixi; rootless per defecte sobre user namespaces, amb les seves quatre limitacions reals i les seves solucions; els pods que assagen el model de Kubernetes al teu portàtil amb localhost entre contenidors; generate kube i play kube com el millor esborrany de manifestos que existeix, perquè parteix d'alguna cosa que funciona; i el socket compatible que et deixa fer servir el docker compose oficial i Testcontainers sense canviar d'eines. I has migrat Aurora Libros de debò: mateixa imatge, mateix digest, mateixa signatura verificada, mateix compose.yaml, amb el cache-aside retornant db i després cache, i systemd d'usuari ocupant el lloc de restart: unless-stopped.

Completen el quadre containerd —la peça que ja feies servir sense saber-ho— amb ctr per depurar i nerdctl per treballar, inclòs nerdctl compose; Buildah per construir sense daemon; Skopeo per inspeccionar i copiar entre registres sense descarregar res, amb l'--all que evita replicar mitja imatge; i gVisor i Kata quan cal executar codi que no controles i el cost de rendiment es paga de gust. La taula de decisió et diu quan Docker continua sent l'elecció natural i quan compensa una altra cosa.

A l'última lliçó del curs aixequem la vista del tot: cap on va l'ecosistema de contenidors, quines tendències tenen substància i quines són aposta, què no canviarà —i per això és on convé invertir l'aprenentatge—, i el tancament complet del camí que ha recorregut Aurora Libros des d'aquells quinze passos manuals d'onboarding.

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