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
- La pregunta correcta: de qui és una imatge?
- L'estàndard OCI i les seves tres especificacions
- Què significa això a la pràctica
- El mapa de peces: runtimes de baix i d'alt nivell
- CRI: per què Kubernetes no parla amb Docker
- Podman: l'arquitectura sense daemon
- Rootless per defecte
- Compatibilitat de comandes i què es trenca
- Pods,
generate kubeiplay kube - Compose, el socket compatible i Testcontainers
- Docker davant de Podman, i la migració d'Aurora Libros
- containerd i
nerdctl - Buildah i Skopeo
- Aïllament reforçat: gVisor i Kata Containers
- Taula de decisió
- 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.
- 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.
- 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.
- 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" } } }
- 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çó.
- 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.
- 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 usuariEl 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 |
- 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 igualFunciona 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 |
- Pods,
generate kube i play kube
generate kube i play kubePodman 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... 3Fixa'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ústergenerate 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.
- 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 PodmanL'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 |
Sí, via socket | La millor opció |
podman-compose |
Parcialment | Claus avançades no cobertes |
| Testcontainers | Sí | Desactivar Ryuk o configurar-lo |
Trivy, dive, hadolint |
Sí | Llegeixen imatges pel socket o del registre |
| Portainer | Sí, amb matisos | Apunta al socket de Podman |
- 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 | Sí, 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.
- containerd i
nerdctl
nerdctlcontainerd é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 nivellnerdctl é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.
- 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.0Skopeo é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.0L'--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.
- 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'hostPer 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ó.
- 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 podmanno ho veupodman. És la confusió número u en començar. - Esperar
restart: alwaysa Podman. No hi ha daemon. Genera unitats ambpodman generate systemdi no oblidisloginctl enable-linger. - Publicar el port 80 en rootless sense ajustar res. Els ports privilegiats estan vetats. Ajusta
ip_unprivileged_port_starto posa un proxy al davant. - Copiar imatges amb
skopeo copysense--all. T'endús només una arquitectura i l'altre entorn falla en desplegar. - Fer servir
ctrper al dia a dia. És una eina de depuració de containerd. Fes servirnerdctl. - Consell: executa
docker buildx imagetools inspect --rawsobre la teva imatge i llegeix elmediaType. Veurevnd.ocia 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.comIdè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.yamlEl 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 10Les 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
- 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
