Tienes una imagen profesional en tu máquina: auroralibros/aurora-api:1.1.0, con usuario sin privilegios, etiquetas OCI, healthcheck y una parada limpia en 0,3 segundos. Y sabes mantener el almacén donde vive. Le falta lo único que justificaba todo el módulo: salir de tu portátil. Una imagen que solo existe en la máquina donde se construyó no resuelve el problema de Aurora Libros; resuelve tu problema local, que no es lo mismo. Esta lección cierra el ciclo. Vas a entender qué hace exactamente docker image tag —que no es copiar—, vas a decidir una estrategia de etiquetado que no te explote dentro de seis meses, vas a publicar en Docker Hub y también en GitHub Container Registry, y vas a aprender por qué en producción se despliega por etiqueta inmutable o por digest y nunca, jamás, por latest. Al terminar, cualquier persona del mundo con Docker instalado podrá ejecutar la API de Aurora Libros con un solo comando.

Contenido

  1. docker image tag: referencias, no copias
  2. El nombre completo y el registro implícito
  3. Estrategia de etiquetado
  4. latest y por qué no se despliega con él
  5. Publicar en Docker Hub con docker push
  6. Verificar lo publicado
  7. Publicar en GitHub Container Registry
  8. Repositorios privados y acceso de equipo
  9. Borrar etiquetas y por qué rompe despliegues
  10. Convenciones de nomenclatura para Aurora Libros

  1. docker image tag: referencias, no copias

Empecemos por deshacer el malentendido más extendido. docker image tag no copia una imagen, no duplica capas y no ocupa espacio adicional. Crea un nombre nuevo que apunta a la misma imagen.

docker image tag auroralibros/aurora-api:1.1.0 auroralibros/aurora-api:1.1
docker image tag auroralibros/aurora-api:1.1.0 auroralibros/aurora-api:1
docker image tag auroralibros/aurora-api:1.1.0 auroralibros/aurora-api:latest
docker image ls auroralibros/aurora-api --format "table {{.Tag}}\t{{.ID}}\t{{.Size}}"
TAG      IMAGE ID       SIZE
1        4e9c7d2a8f31   167MB
1.1      4e9c7d2a8f31   167MB
1.1.0    4e9c7d2a8f31   167MB
latest   4e9c7d2a8f31   167MB

Cuatro filas, un solo IMAGE ID. Son cuatro nombres de la misma imagen. Y el disco lo confirma:

docker system df --format "table {{.Type}}\t{{.TotalCount}}\t{{.Size}}" | head -2
TYPE      TOTAL     SIZE
Images    9         842.3MB

Antes de crear las tres etiquetas eran 842,3 MB; después, exactamente los mismos 842,3 MB. Es la misma demostración que hiciste en el ejercicio 2 de la lección anterior, y el mismo modelo del apartado 1 de la 02-01: las etiquetas son punteros a digests, no contenedores de datos.

La sintaxis:

docker image tag <origen>[:etiqueta] <destino>[:etiqueta]

El origen puede ser un nombre existente o un IMAGE ID:

docker image tag 4e9c7d2a8f31 auroralibros/aurora-api:candidata
docker image tag auroralibros/aurora-api:1.1.0 ghcr.io/auroralibros/aurora-api:1.1.0
docker image tag auroralibros/aurora-api:1.1.0 registro.interno.auroralibros.example/aurora-api:1.1.0

Las tres son gratis e instantáneas. La consecuencia práctica es importante: puedes publicar la misma imagen en varios registros sin reconstruirla. Lo harás en el apartado 7.

Reglas de los nombres de etiqueta:

Regla Válido No válido
Hasta 128 caracteres 1.1.0-rc.1 Una cadena de 200 caracteres
Letras, dígitos, _, ., - v1.1.0, main_2026-08-04 1.1.0/rc, versión-1
No puede empezar por . ni - 1.1.0 .oculta, -beta
Mayúsculas permitidas en la etiqueta 1.1.0-RC1
El nombre del repositorio va en minúsculas aurora-api Aurora-API

La última fila causa errores reales: docker push AuroraLibros/Aurora-API falla con invalid reference format: repository name must be lowercase.

  1. El nombre completo y el registro implícito

Ya lo viste en la lección 02-01, pero ahora es cuando importa de verdad, porque el nombre determina dónde va el push:

[registro/][usuario/]repositorio[:etiqueta]
docker push auroralibros/aurora-api:1.1.0

Docker expande ese nombre a docker.io/auroralibros/aurora-api:1.1.0 y envía la imagen a Docker Hub. No hay ninguna opción --registry: si quieres publicar en otro sitio, cambias el nombre.

Nombre Destino del push
aurora-api:1.1.0 docker.io/library/aurora-apifalla: nadie puede escribir en library/
auroralibros/aurora-api:1.1.0 Docker Hub, espacio auroralibros
ghcr.io/auroralibros/aurora-api:1.1.0 GitHub Container Registry
localhost:5000/aurora-api:1.1.0 Tu registro local de la lección 02-01
123456789012.dkr.ecr.eu-west-1.amazonaws.com/aurora-api:1.1.0 Amazon ECR

La primera fila es el error denied: requested access to the resource is denied que diagnosticaste en el ejercicio 3 de la lección 02-01. Ahora ya sabes que casi nunca es un problema de credenciales: es un nombre sin espacio de nombres.

Docker distingue el registro del usuario con una heurística sencilla: si el primer segmento contiene un punto o dos puntos, o es exactamente localhost, lo trata como nombre de servidor. Por eso ghcr.io/… es un registro y auroralibros/… es un usuario de Docker Hub.

  1. Estrategia de etiquetado

Aquí está la decisión que más problemas evita a medio plazo. Etiquetar mal no rompe nada hoy; rompe todo dentro de seis meses, cuando nadie sepa qué versión hay en producción.

Versionado semántico: etiquetas fijas y móviles

El versionado semántico (MAYOR.MENOR.PARCHE) da tres números con significado:

Componente Cuándo sube Ejemplo en Aurora Libros
MAYOR Cambio incompatible en la API /libros cambia el formato de respuesta
MENOR Funcionalidad nueva compatible Se añade /libros/buscar
PARCHE Corrección compatible Se arregla el cálculo del precio con IVA

Y de ahí nacen dos clases de etiqueta que hay que distinguir con claridad:

Clase Ejemplos ¿Cambia a qué imagen apunta? Para qué sirve
Fija (inmutable) 1.1.0, 1.1.0-rc.1, sha-7a3f912 Nunca Desplegar, reproducir, auditar
Móvil 1.1, 1, latest, estable Sí, con cada publicación Comodidad, seguir una serie

Publicar una versión típica de Aurora Libros:

docker image tag auroralibros/aurora-api:1.1.0 auroralibros/aurora-api:1.1
docker image tag auroralibros/aurora-api:1.1.0 auroralibros/aurora-api:1
docker image tag auroralibros/aurora-api:1.1.0 auroralibros/aurora-api:latest

Con esto, quien consume la imagen elige su nivel de riesgo:

flowchart LR
    subgraph HOY["Publicación de 1.1.0"]
        T1["1.1.0"] --> IMG1["sha256:4e9c…"]
        T2["1.1"] --> IMG1
        T3["1"] --> IMG1
        T4["latest"] --> IMG1
    end

    subgraph MANANA["Publicación de 1.1.1 (parche)"]
        U1["1.1.0"] --> IMG1b["sha256:4e9c…<br/>INTACTA"]
        U2["1.1.1"] --> IMG2["sha256:8b3f…"]
        U3["1.1"] --> IMG2
        U4["1"] --> IMG2
        U5["latest"] --> IMG2
    end

    HOY --> MANANA

Fíjate en lo esencial del diagrama: al publicar 1.1.1, la etiqueta 1.1.0 sigue apuntando exactamente a la misma imagen de siempre. Las móviles se han desplazado. Quien desplegó 1.1.0 no se ve afectado por nada; quien usa 1.1 recibe el parche automáticamente.

Otras estrategias

Estrategia Ejemplo Ventajas Inconvenientes
SemVer fijo 1.1.0 Trazable, reproducible, es el estándar Hay que decidir el número en cada release
SemVer móvil 1.1, 1 Parches automáticos sin tocar el despliegue No sabes qué imagen exacta corre
SHA de commit sha-7a3f912 Trazabilidad perfecta al código; automática en CI Ilegible para humanos
Por rama main, develop, feature-buscar Cómoda en desarrollo Móvil por definición; nunca en producción
Por entorno produccion, staging Simple de entender Antipatrón: oculta qué versión hay desplegada
Por fecha 2026-08-04, 20260804-1142 Orden cronológico obvio No dice nada del contenido
latest latest Comodidad al probar Nunca en producción (apartado 4)

La estrategia por entorno merece una advertencia especial, porque parece razonable y es una trampa. Si despliegas auroralibros/aurora-api:produccion, la pregunta "¿qué versión hay en producción?" no tiene respuesta: la etiqueta se ha movido veinte veces y nadie sabe a qué apunta hoy. Y un rollback es imposible, porque la imagen anterior perdió su nombre. Lo correcto es al revés: la imagen se etiqueta por versión, y qué versión va a cada entorno lo decide el fichero de despliegue, versionado en Git.

La combinación recomendada

En la práctica, un pipeline maduro publica varias etiquetas de la misma imagen en cada release:

VERSION=1.1.0
COMMIT=$(git rev-parse --short HEAD)
IMAGEN=auroralibros/aurora-api

docker build -t $IMAGEN:$VERSION \
             -t $IMAGEN:sha-$COMMIT \
             -t $IMAGEN:1.1 \
             -t $IMAGEN:1 \
             -t $IMAGEN:latest \
             --build-arg VERSION=$VERSION \
             --build-arg REVISION=$COMMIT \
             --build-arg CREATED="$(date -u +%Y-%m-%dT%H:%M:%SZ)" .

Recuerda de la lección 02-02 que -t es repetible: una sola build, cinco nombres, cero coste adicional. Los --build-arg alimentan las etiquetas OCI de la lección 02-04, de modo que la imagen lleva dentro la misma información que declara fuera. Esa coherencia es lo que permite responder "¿qué código lleva esto?" con un solo docker inspect.

  1. latest y por qué no se despliega con él

latest no tiene ningún significado especial para Docker. No es "la más reciente", no se actualiza sola y no la calcula nadie: es simplemente la etiqueta que se usa por defecto cuando no escribes ninguna. Si nadie publica una etiqueta llamada latest, no existe.

Los problemas de desplegar con ella:

  1. No es reproducible. docker run auroralibros/aurora-api:latest hoy y mañana pueden ejecutar código distinto. Si mañana falla, no puedes reproducir el estado de hoy.
  2. Rompe los rollbacks. "Vuelve a la versión anterior" no tiene sentido si la única referencia es latest.
  3. Puede mentir. Si publicas 1.2.0 y luego un parche 1.1.1 de la rama antigua sin cuidado, latest puede acabar apuntando a la versión más vieja. Es una etiqueta manual, no un cálculo.
  4. Provoca despliegues heterogéneos. Cinco réplicas arrancadas en momentos distintos con latest pueden estar ejecutando tres versiones diferentes a la vez.
  5. Impide auditar. ¿Qué código hay en producción? "Latest." No es una respuesta.

La regla de oro: en producción se despliega por etiqueta inmutable o por digest. Nunca por latest, ni por ninguna etiqueta móvil.

Desplegar por digest

El nivel máximo de garantía es referenciar el digest del manifiesto:

docker pull auroralibros/aurora-api:1.1.0
docker image ls --digests auroralibros/aurora-api --format "{{.Tag}}\t{{.Digest}}"
1.1.0    sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3

Y se despliega así:

docker pull auroralibros/aurora-api@sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3
docker run -d --name aurora-api \
  auroralibros/aurora-api@sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3

Comparación de garantías:

Referencia ¿Reproducible? Legible Uso
:latest No Pruebas rápidas y nada más
:1.1 No (se mueve con los parches) Desarrollo
:1.1.0 por convenio (nada impide reescribirla) Producción normal
@sha256:c4f8… Sí, criptográficamente No Producción crítica, cadena de suministro auditada

La fila de 1.1.0 tiene un matiz que conviene entender: nada en Docker impide que alguien vuelva a publicar 1.1.0 apuntando a otra imagen. Es una convención de equipo, no una garantía técnica. El digest sí lo es: sha256:c4f8… es el hash del manifiesto, y si el contenido cambiara, el hash sería otro. Por eso los sistemas de despliegue serios (Kubernetes en entornos regulados, lección 06-05) referencian por digest.

Puedes obtener el digest de una imagen publicada sin descargarla:

docker buildx imagetools inspect auroralibros/aurora-api:1.1.0 --format "{{.Manifest.Digest}}"

  1. Publicar en Docker Hub con docker push

Momento de la verdad. Recuerda del apartado 10 de la lección 02-01 que el repositorio elegido es auroralibros/aurora-api, público.

Paso 1: autenticarse

docker login -u auroralibros
Password:
Login Succeeded

En el campo de contraseña pega el token de acceso personal con permiso Read & Write, no tu contraseña, por todo lo explicado en la lección 02-01.

Paso 2: comprobar que el nombre es correcto

docker image ls auroralibros/aurora-api --format "table {{.Tag}}\t{{.ID}}"
TAG      IMAGE ID
1        4e9c7d2a8f31
1.1      4e9c7d2a8f31
1.1.0    4e9c7d2a8f31
latest   4e9c7d2a8f31

Si tu Docker ID real no es auroralibros, reetiqueta antes:

docker image tag auroralibros/aurora-api:1.1.0 tu-docker-id/aurora-api:1.1.0

Paso 3: auditar antes de publicar

Este paso no es opcional. Una imagen pública es código publicado, y una vez subida no hay marcha atrás real: alguien puede haberla descargado en el minuto siguiente.

# a) ¿Hay algún secreto en las variables de entorno?
docker image inspect auroralibros/aurora-api:1.1.0 --format '{{range .Config.Env}}{{println .}}{{end}}'
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NODE_VERSION=22.14.0
YARN_VERSION=1.22.22
NODE_ENV=production
PORT=3000
APP_VERSION=1.1.0

Ninguna credencial. Correcto.

# b) ¿Hay algún --build-arg con secretos en el historial?
docker image history auroralibros/aurora-api:1.1.0 --no-trunc --format "{{.CreatedBy}}" | grep -iE "password|secret|token|key" || echo "Sin secretos en el historial"
Sin secretos en el historial
# c) ¿Se coló algún fichero que no debía?
docker run --rm --entrypoint ls auroralibros/aurora-api:1.1.0 -la /app
drwxr-xr-x    1 node     node          4096 Aug  4 11:42 .
-rw-r--r--    1 node     node           412 Aug  4 09:15 Dockerfile
drwxr-xr-x  112 node     node          4096 Aug  4 11:42 node_modules
-rw-r--r--    1 node     node         44231 Aug  4 09:15 package-lock.json
-rw-r--r--    1 node     node           412 Aug  4 09:15 package.json
-rw-r--r--    1 node     node          6104 Aug  4 11:20 server.js

Sin .env, sin .git, sin notas locales. El .dockerignore de la lección 02-02 ha hecho su trabajo. (Aparece el Dockerfile si lo has quitado de la lista de exclusiones; no es un problema de seguridad, pero conviene mantenerlo excluido por lo que se explicó allí.)

Paso 4: el push

docker push auroralibros/aurora-api:1.1.0
The push refers to repository [docker.io/auroralibros/aurora-api]
9c1e4a7f2b9d: Pushed
7d3c9f2e8a51: Pushed
2f8e1a9c4b73: Pushed
b8f4e2a91c37: Mounted from library/node
4a1b8c2f9e3d: Mounted from library/node
8f3e1d7c5b9a: Mounted from library/node
e5c2a8f14b76: Mounted from library/node
1.1.0: digest: sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3 size: 1785

Esta salida cuenta una historia, y merece leerse con calma:

Línea Qué significa
Pushed Capa subida de verdad. Son las tres tuyas: los dos COPY y el RUN npm ci
Mounted from library/node Capa que ya estaba en el registro (pertenece a node:22-alpine) y se ha referenciado, no transferido
1.1.0: digest: sha256:… El digest del manifiesto, tu identificador inmutable
size: 1785 El tamaño del manifiesto en bytes, no el de la imagen

Las cuatro líneas Mounted from son la eficiencia de las capas compartidas de la lección 01-05 llevada al registro: de los 167 MB de la imagen, solo has subido unos 25. El resto ya vivía allí porque forma parte de la imagen oficial de Node. Por eso una imagen bien construida sobre una base común se publica en segundos.

Ahora las etiquetas restantes:

docker push auroralibros/aurora-api:1.1
docker push auroralibros/aurora-api:1
docker push auroralibros/aurora-api:latest
The push refers to repository [docker.io/auroralibros/aurora-api]
9c1e4a7f2b9d: Layer already exists
7d3c9f2e8a51: Layer already exists
2f8e1a9c4b73: Layer already exists
b8f4e2a91c37: Layer already exists
...
1.1: digest: sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3 size: 1785

Todas Layer already exists, y el mismo digest que 1.1.0. Es la confirmación de que las etiquetas son punteros: no se han subido datos, solo se han creado tres nombres nuevos en el registro. Las tres publicaciones tardan menos de un segundo entre las tres.

Un atajo que conviene conocer y usar con cuidado:

docker push --all-tags auroralibros/aurora-api

Sube todas las etiquetas locales de ese repositorio. Cómodo, pero peligroso: si tienes una etiqueta local de pruebas (experimento, depuracion), acabará publicada. Mejor empujar explícitamente lo que quieres publicar.

  1. Verificar lo publicado

Nunca des por buena una publicación sin comprobarla.

En la web

Entra en https://hub.docker.com/r/auroralibros/aurora-api y ve a la pestaña Tags. Deberías ver 1.1.0, 1.1, 1 y latest, todas con el mismo tamaño comprimido y la misma fecha. Es la misma pestaña que aprendiste a leer en la lección 02-01, ahora desde el otro lado.

Con docker manifest inspect

docker manifest inspect auroralibros/aurora-api:1.1.0
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.oci.image.manifest.v1+json",
   "config": {
      "mediaType": "application/vnd.oci.image.config.v1+json",
      "size": 3421,
      "digest": "sha256:4e9c7d2a8f31b5c9e2f7a1d4c8b3e6f9a2d5c8b1e4f7a3d6c9b2e5f8a1d4c7b0"
   },
   "layers": [
      {
         "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
         "size": 3622890,
         "digest": "sha256:e5c2a8f14b76..."
      },
      {
         "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
         "size": 24718432,
         "digest": "sha256:2f8e1a9c4b73..."
      }
   ]
}

Este comando consulta el registro remoto, no tu almacén local: es la prueba definitiva de que la imagen está allí. Lo que te dice:

  • mediaType: el formato del manifiesto (OCI, el estándar de la lección 01-03).
  • config.digest: el identificador de la configuración de la imagen.
  • layers: cada capa con su tamaño comprimido y su digest. Ahí se ve que la capa del npm ci son 24,7 MB, coincidiendo con lo que reportaba docker image history en la lección 02-05.

Verifica también que dos etiquetas apuntan a lo mismo:

docker manifest inspect auroralibros/aurora-api:1.1.0 | sha256sum
docker manifest inspect auroralibros/aurora-api:latest | sha256sum
b8e2f4a91c37d5e8a2f6c9b1d4e7a3f8c5b2e9d6a1f4c7b0e3a6d9f2c5b8e1a4  -
b8e2f4a91c37d5e8a2f6c9b1d4e7a3f8c5b2e9d6a1f4c7b0e3a6d9f2c5b8e1a4  -

Manifiestos idénticos: 1.1.0 y latest son la misma imagen.

La prueba real: ejecutarla desde cero

Esta es la que cierra el módulo. Borra la imagen local y descárgala del registro como haría cualquier persona del mundo:

docker image rm auroralibros/aurora-api:1.1.0 auroralibros/aurora-api:1.1 \
                auroralibros/aurora-api:1 auroralibros/aurora-api:latest
docker image ls auroralibros/aurora-api
REPOSITORY   TAG   IMAGE ID   CREATED   SIZE

Vacío. Ahora, desde el registro:

docker run -d --name aurora-publicada -p 3000:3000 auroralibros/aurora-api:1.1.0
Unable to find image 'auroralibros/aurora-api:1.1.0' locally
1.1.0: Pulling from auroralibros/aurora-api
e5c2a8f14b76: Pull complete
2f8e1a9c4b73: Pull complete
...
Status: Downloaded newer image for auroralibros/aurora-api:1.1.0
a7f3c9e2b8d1...
curl -s http://localhost:3000/salud
docker ps --filter name=aurora-publicada --format "{{.Names}}: {{.Status}}"
{"servicio":"aurora-api","version":"1.0.0","db":"ko","cache":"ko",
 "errorDb":"connect ECONNREFUSED 127.0.0.1:5432","errorCache":"connect ECONNREFUSED 127.0.0.1:6379"}
aurora-publicada: Up 8 seconds (health: starting)

La API de Aurora Libros se está ejecutando desde una imagen descargada de Internet. Ese docker run funciona ahora mismo en cualquier máquina del planeta con Docker instalado: sin instalar Node, sin nvm, sin npm install, sin conocer el proyecto. Compáralo con los siete intentos y la hora y media de la lección 01-07.

docker rm -f aurora-publicada

  1. Publicar en GitHub Container Registry

Publicar en un segundo registro es habitual: redundancia, cercanía al código, límites de descarga distintos. Y con lo que sabes, cuesta tres comandos.

Paso 1: crear un token de GitHub

En GitHub: Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token, con estos permisos:

Permiso Para qué
write:packages Publicar imágenes
read:packages Descargarlas
delete:packages Borrar versiones (apartado 9)

Cópialo: no se vuelve a mostrar.

Paso 2: autenticarse

echo "ghp_tuTokenDeEjemplo123456789" | docker login ghcr.io -u auroralibros --password-stdin
Login Succeeded

--password-stdin por lo explicado en la lección 02-01: nada de contraseñas en el historial del shell. Y fíjate en que ahora tienes dos sesiones abiertas a la vez, Docker Hub y ghcr.io, sin que ninguna interfiera con la otra. El destino lo decide el nombre de la imagen, no un estado global.

cat ~/.docker/config.json
{
  "auths": {
    "ghcr.io": {},
    "https://index.docker.io/v1/": {}
  },
  "credsStore": "secretservice"
}

Dos entradas, ambas vacías porque el credential helper guarda las credenciales en el llavero del sistema.

Paso 3: reetiquetar y publicar

docker pull auroralibros/aurora-api:1.1.0    # Si la borraste en el apartado 6

docker image tag auroralibros/aurora-api:1.1.0 ghcr.io/auroralibros/aurora-api:1.1.0
docker image tag auroralibros/aurora-api:1.1.0 ghcr.io/auroralibros/aurora-api:latest

docker push ghcr.io/auroralibros/aurora-api:1.1.0
docker push ghcr.io/auroralibros/aurora-api:latest
The push refers to repository [ghcr.io/auroralibros/aurora-api]
9c1e4a7f2b9d: Pushed
7d3c9f2e8a51: Pushed
2f8e1a9c4b73: Pushed
b8f4e2a91c37: Pushed
4a1b8c2f9e3d: Pushed
8f3e1d7c5b9a: Pushed
e5c2a8f14b76: Pushed
1.1.0: digest: sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3 size: 1785

Dos observaciones importantes:

  1. Todas las capas dicen Pushed, ninguna Mounted from. Es lógico: en ghcr.io no había ninguna copia previa de las capas de node:22-alpine, así que se han subido enteras. La primera publicación en un registro nuevo siempre es la lenta.
  2. El digest es idéntico al de Docker Hub: sha256:c4f81b2e…. La imagen es exactamente la misma en ambos registros, bit a bit. El digest depende del contenido, no de dónde esté alojado.

En GitHub, el paquete aparece en https://github.com/users/auroralibros/packages. Un detalle: por defecto los paquetes de ghcr.io son privados. Para hacerlo público hay que ir a Package settings → Change visibility → Public. Y para enlazarlo con el repositorio de código, basta con que la etiqueta OCI org.opencontainers.image.source de la lección 02-04 apunte al repositorio: GitHub la lee y conecta ambos automáticamente. Ese trabajo de metadatos empieza a rendir aquí.

Comparativa de los dos registros para Aurora Libros:

Aspecto Docker Hub GitHub Container Registry
Repositorios privados en plan gratuito Cupo limitado Ilimitados
Límite de descargas anónimas Sí (lección 02-01) Mucho más laxo
Integración con el código Manual Automática vía la etiqueta OCI source
Descubribilidad pública Máxima Menor
Permisos Por organización Heredados del repositorio de GitHub

Para Aurora Libros, la política razonable: Docker Hub como registro público (descubrible, es donde la gente busca) y ghcr.io como registro de trabajo del equipo, integrado con el código y con privados sin coste.

  1. Repositorios privados y acceso de equipo

Aurora Libros es una empresa: su API real no debería ser pública. Así se cambia.

Hacer privado un repositorio en Docker Hub

  1. Entra en el repositorio → pestaña Settings.
  2. Visibility settingsMake private.
  3. Confirma escribiendo el nombre del repositorio.

El efecto es inmediato:

docker logout
docker pull auroralibros/aurora-api:1.1.0
Error response from daemon: pull access denied for auroralibros/aurora-api,
repository does not exist or may require 'docker login': denied: requested access
to the resource is denied

Es el mensaje ambiguo del ejercicio 3 de la lección 02-01, ahora con la causa que faltaba en aquella lista: el repositorio existe, pero sin sesión no puedes ni saberlo. El registro no distingue entre "no existe" y "no tienes permiso" a propósito, para no filtrar qué repositorios privados tiene una organización.

docker login -u auroralibros
docker pull auroralibros/aurora-api:1.1.0    # Ahora sí

Dar acceso a un equipo

Un repositorio privado bajo una cuenta personal solo lo ve su dueño. Para un equipo hace falta una organización:

  1. Docker Hub → OrganizationsCreate Organization (por ejemplo, auroralibros).
  2. Dentro, Teams → crear equipos por función:
Equipo Permiso Quién
desarrollo Read Desarrolladores: descargan para trabajar en local
ci Read & Write La cuenta de servicio del pipeline: construye y publica
plataforma Admin Responsables: visibilidad, borrados, configuración
  1. Añadir miembros a cada equipo.
  2. En el repositorio → Permissions → asignar cada equipo a su nivel.

Dos principios que hay que respetar:

  • Mínimo privilegio. Una desarrolladora no necesita write. Si nadie más que el pipeline publica, nadie más que el pipeline debe poder hacerlo.
  • Cuentas de servicio para automatización. El pipeline nunca usa las credenciales personales de nadie: usa una cuenta propia con un token de permisos limitados. Si esa persona se va de la empresa, el pipeline no se cae.

En GitHub Container Registry es más sencillo porque hereda del repositorio de código: quien tiene acceso de lectura al repo tiene acceso al paquete, y se ajusta en Package settings → Manage Actions access.

  1. Borrar etiquetas y por qué rompe despliegues

Se puede borrar una etiqueta publicada. Casi nunca se debe.

En Docker Hub: repositorio → pestaña Tags → seleccionar → Delete. Para borrar el repositorio entero, Settings → Delete repository.

En ghcr.io: página del paquete → Package settings → Manage versions → Delete.

Por qué es peligroso

Una etiqueta publicada es una dependencia de otros. Piensa en lo que pasa al borrar auroralibros/aurora-api:1.1.0:

  1. Los despliegues existentes fallan al reiniciarse. Los contenedores en marcha siguen vivos (tienen la imagen en local), pero cualquier máquina nueva, cualquier réplica que escale y cualquier reinicio tras un docker system prune da:
Error response from daemon: manifest for auroralibros/aurora-api:1.1.0 not found:
manifest unknown: manifest unknown
  1. Los rollbacks se vuelven imposibles. La versión anterior era exactamente lo que necesitabas para volver atrás.
  2. Los pipelines se rompen. Cualquier Dockerfile que hiciera FROM auroralibros/aurora-api:1.1.0 deja de construir.
  3. La trazabilidad desaparece. No se puede reproducir un incidente de hace tres meses.

Y una variante aún peor: reescribir una etiqueta publicada, es decir, hacer push de una imagen distinta con el mismo 1.1.0. Nadie se entera, las máquinas que ya la tienen siguen con la vieja, las nuevas descargan la nueva, y acabas con un despliegue donde 1.1.0 significa dos cosas distintas según cuándo arrancó cada réplica. Es una de las incidencias más difíciles de diagnosticar que existen.

Cuándo sí borrar

Situación Acción
Se publicó un secreto por error Borrar inmediatamente y, sobre todo, rotar la credencial: hay que asumir que ya está comprometida
Etiquetas de desarrollo antiguas (pr-142, sha-… de hace meses) Borrado rutinario, con políticas de retención
Versión con un fallo grave de seguridad Mejor publicar 1.1.1 corregida y marcar la anterior como obsoleta en la descripción, que borrarla
Versión estable en uso Nunca

La alternativa correcta al borrado es la deprecación: publicar una versión nueva, documentar en la descripción del repositorio que la anterior no debe usarse y dar un plazo. Se avisa, no se rompe.

Y una advertencia sobre los secretos: borrar la etiqueta no borra el problema. Si publicaste una contraseña dentro de una imagen, cualquiera pudo descargarla en los minutos que estuvo disponible. La única respuesta válida es rotar esa credencial. Por eso el paso 3 del apartado 5 —auditar antes de publicar— no es burocracia.

  1. Convenciones de nomenclatura para Aurora Libros

Cerramos con la decisión escrita, que es lo que un equipo real acuerda y documenta en su repositorio.

Repositorios

Un repositorio por artefacto desplegable, nunca uno por proyecto:

Servicio Repositorio Registro público Registro de equipo
API del catálogo aurora-api docker.io/auroralibros/aurora-api ghcr.io/auroralibros/aurora-api
Web y proxy aurora-web docker.io/auroralibros/aurora-web ghcr.io/auroralibros/aurora-web
Base de datos (no procede) Se usa postgres:16-alpine oficial
Caché (no procede) Se usa redis:7-alpine oficial

Las dos últimas filas son una decisión deliberada: no se reempaquetan imágenes oficiales sin motivo. aurora-db usará postgres:16-alpine tal cual, con su inicialización montada desde fuera (módulo 3). Crear una imagen propia solo para meter dentro un init.sql añade una imagen que mantener a cambio de nada.

Etiquetas

Etiqueta Cuándo se publica ¿Móvil? Uso permitido
1.1.0 En cada release, desde una etiqueta de Git No Producción
1.1 Con cada parche de la serie 1.1 Desarrollo, entornos de prueba
1 Con cada versión menor de la serie 1 Desarrollo
latest Con la última versión estable Pruebas rápidas. Nunca en producción
sha-7a3f912 En cada commit de main, desde CI No Depuración, trazabilidad, rollback fino
1.2.0-rc.1 Candidatas a versión No Entorno de preproducción
pr-142 En cada pull request Revisión; se borra al cerrar la PR

Reglas acordadas

  1. Producción se despliega por etiqueta inmutable (1.1.0) o por digest. Nunca por latest, 1.1 ni 1.
  2. Una etiqueta fija publicada no se reescribe jamás. Si hay un fallo, se publica una versión nueva.
  3. Toda imagen lleva las etiquetas OCI de la lección 02-04, con version y revision coincidiendo con las etiquetas del registro.
  4. Solo la cuenta de servicio de CI publica en producción. Las personas publican, como mucho, etiquetas pr-* y sha-*.
  5. Auditar antes de publicar: variables de entorno, historial y contenido de /app.
  6. Las etiquetas pr-* caducan a los 30 días, con política de retención automática.

Un ejemplo del comando completo de release, que es lo que ejecutará el pipeline de la lección 06-02:

#!/bin/bash
# publicar-release.sh — publica una versión de aurora-api en ambos registros
set -euo pipefail

VERSION="${1:?Uso: publicar-release.sh <version>   ej: 1.1.0}"
COMMIT=$(git rev-parse --short HEAD)
CREATED=$(date -u +%Y-%m-%dT%H:%M:%SZ)
MAYOR="${VERSION%%.*}"
MENOR="${VERSION%.*}"

for REG in "auroralibros" "ghcr.io/auroralibros"; do
  IMG="$REG/aurora-api"
  docker build \
    -t "$IMG:$VERSION" \
    -t "$IMG:$MENOR" \
    -t "$IMG:$MAYOR" \
    -t "$IMG:latest" \
    -t "$IMG:sha-$COMMIT" \
    --build-arg VERSION="$VERSION" \
    --build-arg REVISION="$COMMIT" \
    --build-arg CREATED="$CREATED" \
    --pull \
    ./api

  for T in "$VERSION" "$MENOR" "$MAYOR" "latest" "sha-$COMMIT"; do
    docker push "$IMG:$T"
  done
done

echo "Publicada $VERSION (commit $COMMIT) en Docker Hub y ghcr.io"
docker manifest inspect "auroralibros/aurora-api:$VERSION" --verbose | grep -m1 digest

Fíjate en los detalles: set -euo pipefail aborta ante cualquier error, ${VERSION%%.*} y ${VERSION%.*} derivan 1 y 1.1 de 1.1.0 con expansión de parámetros del shell, --pull garantiza la base actualizada como se explicó en 02-02, y los --build-arg mantienen coherentes las etiquetas OCI internas con las del registro.

Errores Comunes y Consejos

  • Creer que docker image tag copia la imagen. Crea una referencia; el disco no crece. Cuatro etiquetas de la misma imagen ocupan lo que una.
  • docker push aurora-api:1.1.0 sin espacio de nombres. Se expande a library/aurora-api y falla con denied. Ante un denied en un push, mira el nombre antes que las credenciales.
  • Mayúsculas en el nombre del repositorio. invalid reference format: repository name must be lowercase. Solo la etiqueta admite mayúsculas.
  • Desplegar con latest. No es reproducible, impide el rollback, puede apuntar a una versión más antigua y hace imposible auditar qué hay en producción.
  • Etiquetar por entorno (produccion, staging). Antipatrón: la etiqueta se mueve y nadie sabe qué versión corre. Etiqueta por versión y decide en el despliegue.
  • Reescribir una etiqueta fija ya publicada. Provoca despliegues donde 1.1.0 significa cosas distintas según cuándo arrancó cada réplica. Publica una versión nueva.
  • Borrar una etiqueta publicada. Rompe reinicios, escalados y rollbacks de todo el que la use. Depreca en lugar de borrar.
  • Publicar sin auditar. Un secreto publicado hay que darlo por comprometido: borrar la etiqueta no basta, hay que rotar la credencial.
  • docker push --all-tags a la ligera. Publica también tus etiquetas de pruebas locales.
  • Usar la contraseña en lugar de un token. Un token se revoca sin cambiar la contraseña, tiene permisos limitados y puedes tener uno por máquina.
  • Consejo: verifica siempre con docker manifest inspect. Consulta el registro remoto, no tu caché local: es la única prueba real de que la publicación funcionó.
  • Consejo: borra la imagen local y descárgala del registro antes de dar por buena una release. Es la única forma de comprobar que lo publicado es autosuficiente.
  • Consejo: haz que las etiquetas OCI internas coincidan con las del registro. Que org.opencontainers.image.version diga 1.1.0 y la etiqueta sea 1.1.0 te salvará más de una investigación.

Ejercicios

Ejercicio 1: demuestra que las etiquetas son punteros

  1. Anota el resultado de docker system df (línea de Images).
  2. Crea cinco etiquetas nuevas para auroralibros/aurora-api:1.1.0: 1.1, 1, latest, estable y sha-abc1234.
  3. Vuelve a ejecutar docker system df. ¿Ha crecido el espacio? ¿Por qué?
  4. Comprueba que las seis referencias comparten IMAGE ID.
  5. Borra las etiquetas estable y sha-abc1234. ¿Qué mensaje da cada borrado? ¿Y si borraras las seis?
  6. Reconstruye la imagen tras modificar server.js, etiquetándola de nuevo como 1.1.0. ¿Qué pasa con 1.1, 1 y latest? Explica el resultado en términos de punteros.

Ejercicio 2: publica y verifica el ciclo completo

Con tu cuenta real de Docker Hub (sustituye auroralibros por tu Docker ID):

  1. Audita la imagen antes de publicar: variables de entorno, historial en busca de secretos y contenido de /app.
  2. Publica 1.1.0 y latest, y analiza la salida del primer push: ¿cuántas capas dicen Pushed y cuántas Mounted from? ¿Por qué?
  3. Analiza la salida del segundo push. ¿Por qué es instantáneo?
  4. Verifica con docker manifest inspect que ambas etiquetas tienen el mismo digest.
  5. Borra todas las copias locales y ejecuta la API descargándola del registro. Comprueba /salud y el estado del healthcheck.
  6. Obtén el digest y ejecuta la imagen referenciándola por @sha256:… en lugar de por etiqueta.

Ejercicio 3: diseña la estrategia de etiquetado de Aurora Libros

El equipo de Aurora Libros te plantea cuatro situaciones. Para cada una, indica qué etiquetas publicarías, cuál desplegarías en producción y por qué:

a) Release estable de la versión 1.2.0, con una funcionalidad nueva (/libros/buscar) desde el commit 9c4e1a7 de la rama main.

b) Corrección urgente de un fallo de seguridad en la 1.2.0, que hay que desplegar en producción hoy mismo.

c) Una pull request número 87 que añade paginación y hay que probar en el entorno de revisión.

d) Cambio incompatible: /libros pasa a devolver {datos: [...], total: n} en lugar de un array. Rompe la web actual.

Además, responde:

e) Un compañero propone docker push auroralibros/aurora-api:produccion en cada despliegue. Da tres argumentos técnicos concretos para rechazarlo y una alternativa.

Soluciones

Solución al ejercicio 1

# 1
docker system df --format "table {{.Type}}\t{{.TotalCount}}\t{{.Size}}" | head -2
TYPE      TOTAL     SIZE
Images    9         842.3MB
# 2
for T in 1.1 1 latest estable sha-abc1234; do
  docker image tag auroralibros/aurora-api:1.1.0 auroralibros/aurora-api:$T
done

# 3
docker system df --format "table {{.Type}}\t{{.TotalCount}}\t{{.Size}}" | head -2
TYPE      TOTAL     SIZE
Images    9         842.3MB

Ni el recuento ni el tamaño han cambiado. El recuento sigue siendo 9 porque docker system df cuenta imágenes, no referencias, y las seis etiquetas son una sola imagen. docker image tag escribe una entrada en un índice de nombres; no toca ni un byte de las capas.

# 4
docker image ls auroralibros/aurora-api --format "table {{.Tag}}\t{{.ID}}"
TAG           IMAGE ID
1             4e9c7d2a8f31
1.1           4e9c7d2a8f31
1.1.0         4e9c7d2a8f31
estable       4e9c7d2a8f31
latest        4e9c7d2a8f31
sha-abc1234   4e9c7d2a8f31
# 5
docker image rm auroralibros/aurora-api:estable auroralibros/aurora-api:sha-abc1234
Untagged: auroralibros/aurora-api:estable
Untagged: auroralibros/aurora-api:sha-abc1234

Solo Untagged, sin Deleted: quedan cuatro referencias apuntando a la imagen, así que los datos siguen ahí. Si borraras las seis, la última mostraría Untagged y una lista de Deleted: sha256:…, una por capa exclusiva. El Deleted aparece únicamente cuando cae la última referencia, tal como viste en la lección 02-05.

# 6
cd ~/aurora-libros/api
echo "// cambio para el ejercicio" >> server.js
docker build -q -t auroralibros/aurora-api:1.1.0 .
docker image ls auroralibros/aurora-api --format "table {{.Tag}}\t{{.ID}}"
TAG      IMAGE ID
1.1.0    b3f7e2a91c48
1        4e9c7d2a8f31
1.1      4e9c7d2a8f31
latest   4e9c7d2a8f31

Solo 1.1.0 se ha movido a la imagen nueva. Las otras tres siguen apuntando a la vieja, que ya no es dangling porque conserva nombres. Esto demuestra que etiquetar es un acto explícito: Docker no propaga nada. Si quieres que 1.1, 1 y latest sigan a la nueva versión, hay que reetiquetarlas a mano (o generar todas las etiquetas en el mismo docker build con varios -t, como en el script del apartado 10).

Y ojo con lo que acabas de hacer sin querer: has reescrito el contenido de la etiqueta 1.1.0. En local es inocuo; publicada, sería exactamente el antipatrón del apartado 9.

Solución al ejercicio 2

# 1. Auditoría previa
docker image inspect auroralibros/aurora-api:1.1.0 --format '{{range .Config.Env}}{{println .}}{{end}}'
docker image history auroralibros/aurora-api:1.1.0 --no-trunc --format "{{.CreatedBy}}" \
  | grep -iE "password|secret|token|api[_-]?key" || echo "OK: sin secretos"
docker run --rm --entrypoint ls auroralibros/aurora-api:1.1.0 -la /app
OK: sin secretos
# 2
docker login -u auroralibros
docker push auroralibros/aurora-api:1.1.0
9c1e4a7f2b9d: Pushed
7d3c9f2e8a51: Pushed
2f8e1a9c4b73: Pushed
b8f4e2a91c37: Mounted from library/node
4a1b8c2f9e3d: Mounted from library/node
8f3e1d7c5b9a: Mounted from library/node
e5c2a8f14b76: Mounted from library/node
1.1.0: digest: sha256:c4f81b2e9a7d... size: 1785

Tres Pushed y cuatro Mounted from. Las tres subidas son las capas que genera tu Dockerfile: COPY package*.json, RUN npm ci y COPY . .. Las cuatro montadas pertenecen a node:22-alpine, que ya estaba en Docker Hub: el registro las referencia en lugar de recibirlas. De 167 MB de imagen se transfieren unos 25. Es el mismo ahorro de las capas compartidas de la lección 01-05, ahora en la red.

# 3
docker push auroralibros/aurora-api:latest
9c1e4a7f2b9d: Layer already exists
...
latest: digest: sha256:c4f81b2e9a7d... size: 1785

Instantáneo porque no se transfiere nada: todas las capas ya están en el registro y el manifiesto es idéntico. Lo único que ocurre es que se crea un nombre nuevo apuntando al mismo digest. Publicar diez etiquetas de la misma imagen cuesta prácticamente lo mismo que publicar una.

# 4
docker manifest inspect auroralibros/aurora-api:1.1.0 | sha256sum
docker manifest inspect auroralibros/aurora-api:latest | sha256sum
b8e2f4a91c37d5e8a2f6c9b1d4e7a3f8c5b2e9d6a1f4c7b0e3a6d9f2c5b8e1a4  -
b8e2f4a91c37d5e8a2f6c9b1d4e7a3f8c5b2e9d6a1f4c7b0e3a6d9f2c5b8e1a4  -

Idénticos.

# 5
docker image rm -f $(docker image ls -q auroralibros/aurora-api)
docker image ls auroralibros/aurora-api
docker run -d --name desde-registro -p 3000:3000 auroralibros/aurora-api:1.1.0
sleep 45
curl -s http://localhost:3000/salud
docker ps --filter name=desde-registro --format "{{.Names}}: {{.Status}}"
(listado vacío)
Unable to find image 'auroralibros/aurora-api:1.1.0' locally
1.1.0: Pulling from auroralibros/aurora-api
...
{"servicio":"aurora-api","version":"1.0.0","db":"ko","cache":"ko",...}
desde-registro: Up 45 seconds (unhealthy)

Funciona desde cero, y el healthcheck de la lección 02-04 hace su trabajo: unhealthy porque no hay PostgreSQL ni Redis, exactamente lo esperado hasta el módulo 3.

# 6. Por digest
DIGEST=$(docker image ls --digests auroralibros/aurora-api --format "{{.Digest}}" | head -1)
echo "$DIGEST"
docker rm -f desde-registro
docker run -d --name por-digest -p 3000:3000 "auroralibros/aurora-api@$DIGEST"
docker ps --filter name=por-digest --format "{{.Image}}"
sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3
auroralibros/aurora-api@sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3

Fíjate en la columna IMAGE de docker ps: muestra el digest, no una etiqueta. Ese contenedor está atado criptográficamente a un contenido concreto, y ninguna reescritura de etiquetas en el registro puede cambiar lo que ejecuta.

docker rm -f por-digest

Solución al ejercicio 3

(a) Release estable 1.2.0 desde 9c4e1a7:

docker build -t auroralibros/aurora-api:1.2.0 \
             -t auroralibros/aurora-api:1.2 \
             -t auroralibros/aurora-api:1 \
             -t auroralibros/aurora-api:latest \
             -t auroralibros/aurora-api:sha-9c4e1a7 \
             --build-arg VERSION=1.2.0 --build-arg REVISION=9c4e1a7 \
             --pull ./api

Es una funcionalidad compatible (añade un endpoint sin cambiar los existentes), así que sube el número MENOR. En producción se despliega 1.2.0, la etiqueta inmutable. Las móviles 1.2, 1 y latest se publican para comodidad de quien desarrolla, y sha-9c4e1a7 da trazabilidad exacta al commit.

(b) Corrección urgente de seguridad:

docker build -t auroralibros/aurora-api:1.2.1 \
             -t auroralibros/aurora-api:1.2 \
             -t auroralibros/aurora-api:1 \
             -t auroralibros/aurora-api:latest \
             -t auroralibros/aurora-api:sha-4b8f2c1 \
             --build-arg VERSION=1.2.1 --build-arg REVISION=4b8f2c1 \
             --pull --no-cache ./api

Sube el PARCHE: es una corrección compatible. Se despliega 1.2.1. Tres decisiones importantes:

  • No se toca 1.2.0. Sigue existiendo y sigue apuntando a la imagen antigua. Eso permite un rollback inmediato si 1.2.1 sale mal, y es toda la diferencia entre un incidente controlado y uno que se agrava.
  • --no-cache, porque se trata de un parche de seguridad y no quieres reutilizar capas que puedan contener la versión vulnerable de una dependencia.
  • --pull, para que la base node:22-alpine traiga también sus parches.

Si la vulnerabilidad estuviera en la propia 1.2.0, la respuesta correcta no es borrar esa etiqueta (rompería a todo el que la use, incluidos los que aún no han podido actualizar), sino documentarla como obsoleta en la descripción del repositorio y comunicar la actualización.

(c) Pull request 87:

docker build -t auroralibros/aurora-api:pr-87 \
             -t auroralibros/aurora-api:sha-e1d7a92 \
             --build-arg VERSION=1.2.1-pr87 --build-arg REVISION=e1d7a92 ./api

Etiqueta efímera pr-87, móvil (se sobrescribe con cada push a la rama de la PR), y una sha-* inmutable para poder reproducir exactamente lo que se probó. No se publican latest, 1.2 ni ninguna móvil de la serie estable: contaminarían el canal que consumen otros. Al cerrar la PR, pr-87 se borra; es uno de los pocos borrados de etiqueta legítimos, porque nadie la usa en producción por definición.

(d) Cambio incompatible en /libros:

docker build -t auroralibros/aurora-api:2.0.0-rc.1 \
             -t auroralibros/aurora-api:sha-a72f9c3 ./api

Sube el número MAYOR, porque rompe a los consumidores existentes (la web actual espera un array). Y se publica primero como candidata 2.0.0-rc.1, no como estable.

Además, latest no se mueve todavía. Este es el matiz que más equipos olvidan: si latest saltara a la 2.0.0, cualquiera que estuviera usando latest en un entorno de pruebas vería romperse su web sin haber cambiado nada. La secuencia correcta es: publicar la RC, actualizar aurora-web para consumir el formato nuevo, probar ambos juntos, y solo entonces publicar 2.0.0 y mover latest y 2. La etiqueta 1 sigue apuntando a la serie 1 para quien necesite tiempo para migrar.

(e) Tres argumentos contra :produccion:

  1. No es reproducible ni auditable. La pregunta "¿qué código hay en producción?" deja de tener respuesta: la etiqueta se ha movido cincuenta veces y no queda registro de a qué apunta hoy ni de a qué apuntaba durante el incidente del martes pasado.
  2. Imposibilita el rollback. Al mover :produccion a la versión nueva, la anterior pierde su única referencia. Volver atrás exige saber el IMAGE ID o el digest antiguo, y sin registro escrito de eso, no hay marcha atrás.
  3. Provoca despliegues heterogéneos. Con imagePullPolicy: Always o tras un reinicio, unas réplicas descargan la versión nueva y otras conservan la vieja: la misma etiqueta ejecutando dos códigos a la vez, con errores intermitentes imposibles de reproducir.

Un cuarto argumento, si hace falta: acopla la imagen al entorno, rompiendo el principio de "construir una vez, desplegar en todas partes". El mismo artefacto debe valer para pruebas y para producción; lo que cambia es la configuración inyectada, no la imagen.

La alternativa: etiquetar por versión (1.2.1) y guardar en Git, dentro del repositorio de despliegue, qué versión corresponde a cada entorno:

# despliegue/produccion.yaml
imagen: auroralibros/aurora-api:1.2.1

# despliegue/staging.yaml
imagen: auroralibros/aurora-api:1.3.0-rc.2

Así, "qué hay en producción" se responde con un git log de un fichero, el rollback es revertir un commit, y el historial completo de despliegues queda auditado. Es exactamente el planteamiento que se desarrollará en la lección 06-07, sobre estrategias de despliegue y rollback.

Conclusión

Has cerrado el ciclo. docker image tag no copia nada: crea referencias, y cuatro etiquetas de la misma imagen ocupan exactamente lo que una, como confirmó docker system df sin moverse un byte. El nombre completo registro/usuario/repositorio:etiqueta es lo que decide el destino de un push —no existe ninguna opción --registry—, y por eso un denied casi siempre es un nombre sin espacio de nombres antes que un problema de credenciales. Has distinguido las etiquetas fijas de las móviles, has visto por qué etiquetar por entorno es un antipatrón que borra la trazabilidad, y te llevas la regla que más disgustos evita: en producción se despliega por etiqueta inmutable o por digest, nunca por latest, con @sha256:… como única garantía criptográfica real.

Y has publicado. auroralibros/aurora-api:1.1.0 vive en Docker Hub y en GitHub Container Registry, con el mismo digest en ambos porque la imagen es idéntica bit a bit. Sabes leer la salida de un push —Pushed para tus tres capas, Mounted from library/node para las cuatro que el registro ya tenía, y de 167 MB solo viajaron unos 25—, verificarla contra el registro remoto con docker manifest inspect, hacer privado un repositorio y repartir permisos por equipos con el principio de mínimo privilegio. Y sabes por qué borrar una etiqueta publicada rompe reinicios, escalados y rollbacks ajenos, y por qué ante un secreto filtrado lo único que sirve es rotar la credencial.

Haz balance de lo que ha cambiado en este módulo. Empezaste con un repositorio de código, quince pasos manuales de onboarding y una API que solo arrancaba en máquinas con Node 22, nvm, PostgreSQL y Redis instalados a mano. Terminas con una imagen publicada que se ejecuta con un solo comando en cualquier máquina del mundo con Docker: sin instalar Node, sin npm install, sin conocer el proyecto. Por el camino has entendido el contexto de construcción y lo has recortado de 23,41 MB a 47,83 kB con un .dockerignore, has domado la caché de capas hasta pasar de 46,7 a 1,4 segundos por build, has escrito un Dockerfile línea a línea justificando cada decisión, lo has profesionalizado con usuario sin privilegios, metadatos OCI y un healthcheck real contra /salud, y has aprendido a mantener tu almacén de imágenes sin destruir lo que importa. Los pasos 1 a 5 de aquella lista de quince han desaparecido.

Pero tu imagen sigue devolviendo ECONNREFUSED en /libros, y ese fallo lleva tres lecciones esperándote. Dentro del contenedor, localhost es el propio contenedor: no hay ningún PostgreSQL ni ningún Redis ahí dentro, y por eso el healthcheck marca unhealthy con toda la razón. En el módulo 3, Contenedores Docker, esos contenedores dejan de ser demostraciones aisladas y empiezan a funcionar de verdad: dominarás las opciones de docker run y el ciclo de vida completo, aprenderás a inspeccionar y depurar un contenedor por dentro, crearás una red propia donde aurora-api encuentre a aurora-db llamándola por su nombre, darás a PostgreSQL un volumen para que el catálogo de ocho libros sobreviva a la destrucción del contenedor, y pondrás límites de memoria y políticas de reinicio a los cuatro servicios. Al terminarlo, curl http://localhost:3000/libros devolverá por fin El jardín de senderos que se bifurcan, Rayuela y los otros seis títulos, servidos desde una base de datos real en un contenedor real. La plataforma de Aurora Libros empezará a estar viva.

Docker: De Principiante a Avanzado

Módulo 1: Introducción a Docker

Módulo 2: Trabajando con Imágenes Docker

Módulo 3: Contenedores Docker

Módulo 4: Docker Compose

Módulo 5: Conceptos Avanzados de Docker

Módulo 6: Docker en Producción

Módulo 7: Ecosistema y Herramientas de Docker

© Copyright 2026. Todos los derechos reservados