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
docker image tag: referencias, no copias- El nombre completo y el registro implícito
- Estrategia de etiquetado
latesty por qué no se despliega con él- Publicar en Docker Hub con
docker push - Verificar lo publicado
- Publicar en GitHub Container Registry
- Repositorios privados y acceso de equipo
- Borrar etiquetas y por qué rompe despliegues
- Convenciones de nomenclatura para Aurora Libros
docker image tag: referencias, no copias
docker image tag: referencias, no copiasEmpecemos 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 167MBCuatro filas, un solo IMAGE ID. Son cuatro nombres de la misma imagen. Y el disco lo confirma:
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:
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.0Las 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.
- 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:
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-api → falla: 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.
- 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:latestCon 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.
latest y por qué no se despliega con él
latest y por qué no se despliega con éllatest 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:
- No es reproducible.
docker run auroralibros/aurora-api:latesthoy y mañana pueden ejecutar código distinto. Si mañana falla, no puedes reproducir el estado de hoy. - Rompe los rollbacks. "Vuelve a la versión anterior" no tiene sentido si la única referencia es
latest. - Puede mentir. Si publicas
1.2.0y luego un parche1.1.1de la rama antigua sin cuidado,latestpuede acabar apuntando a la versión más vieja. Es una etiqueta manual, no un cálculo. - Provoca despliegues heterogéneos. Cinco réplicas arrancadas en momentos distintos con
latestpueden estar ejecutando tres versiones diferentes a la vez. - 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}}"Y se despliega así:
docker pull auroralibros/aurora-api@sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3
docker run -d --name aurora-api \
auroralibros/aurora-api@sha256:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3Comparación de garantías:
| Referencia | ¿Reproducible? | Legible | Uso |
|---|---|---|---|
:latest |
No | Sí | Pruebas rápidas y nada más |
:1.1 |
No (se mueve con los parches) | Sí | Desarrollo |
:1.1.0 |
Sí por convenio (nada impide reescribirla) | Sí | 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:
- Publicar en Docker Hub con
docker push
docker pushMomento 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
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
Si tu Docker ID real no es auroralibros, reetiqueta antes:
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.0Ninguna 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"# c) ¿Se coló algún fichero que no debía?
docker run --rm --entrypoint ls auroralibros/aurora-api:1.1.0 -la /appdrwxr-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.jsSin .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
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: 1785Esta 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:latestThe 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: 1785Todas 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:
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.
- 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
{
"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 delnpm cison 24,7 MB, coincidiendo con lo que reportabadocker image historyen 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 | sha256sumb8e2f4a91c37d5e8a2f6c9b1d4e7a3f8c5b2e9d6a1f4c7b0e3a6d9f2c5b8e1a4 -
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-apiVacío. Ahora, desde el registro:
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"}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.
- 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
--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.
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:latestThe 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: 1785Dos observaciones importantes:
- Todas las capas dicen
Pushed, ningunaMounted from. Es lógico: enghcr.iono había ninguna copia previa de las capas denode:22-alpine, así que se han subido enteras. La primera publicación en un registro nuevo siempre es la lenta. - 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.
- 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
- Entra en el repositorio → pestaña Settings.
- Visibility settings → Make private.
- Confirma escribiendo el nombre del repositorio.
El efecto es inmediato:
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 deniedEs 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.
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:
- Docker Hub → Organizations → Create Organization (por ejemplo,
auroralibros). - 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 |
- Añadir miembros a cada equipo.
- 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.
- 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:
- 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 pruneda:
Error response from daemon: manifest for auroralibros/aurora-api:1.1.0 not found:
manifest unknown: manifest unknown- Los rollbacks se vuelven imposibles. La versión anterior era exactamente lo que necesitabas para volver atrás.
- Los pipelines se rompen. Cualquier Dockerfile que hiciera
FROM auroralibros/aurora-api:1.1.0deja de construir. - 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.
- 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 | Sí | Desarrollo, entornos de prueba |
1 |
Con cada versión menor de la serie 1 | Sí | Desarrollo |
latest |
Con la última versión estable | Sí | 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 | Sí | Revisión; se borra al cerrar la PR |
Reglas acordadas
- Producción se despliega por etiqueta inmutable (
1.1.0) o por digest. Nunca porlatest,1.1ni1. - Una etiqueta fija publicada no se reescribe jamás. Si hay un fallo, se publica una versión nueva.
- Toda imagen lleva las etiquetas OCI de la lección 02-04, con
versionyrevisioncoincidiendo con las etiquetas del registro. - Solo la cuenta de servicio de CI publica en producción. Las personas publican, como mucho, etiquetas
pr-*ysha-*. - Auditar antes de publicar: variables de entorno, historial y contenido de
/app. - 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 digestFí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 tagcopia 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.0sin espacio de nombres. Se expande alibrary/aurora-apiy falla condenied. Ante undenieden 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.0significa 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-tagsa 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.versiondiga1.1.0y la etiqueta sea1.1.0te salvará más de una investigación.
Ejercicios
Ejercicio 1: demuestra que las etiquetas son punteros
- Anota el resultado de
docker system df(línea de Images). - Crea cinco etiquetas nuevas para
auroralibros/aurora-api:1.1.0:1.1,1,latest,estableysha-abc1234. - Vuelve a ejecutar
docker system df. ¿Ha crecido el espacio? ¿Por qué? - Comprueba que las seis referencias comparten IMAGE ID.
- Borra las etiquetas
estableysha-abc1234. ¿Qué mensaje da cada borrado? ¿Y si borraras las seis? - Reconstruye la imagen tras modificar
server.js, etiquetándola de nuevo como1.1.0. ¿Qué pasa con1.1,1ylatest? 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):
- Audita la imagen antes de publicar: variables de entorno, historial en busca de secretos y contenido de
/app. - Publica
1.1.0ylatest, y analiza la salida del primer push: ¿cuántas capas dicenPushedy cuántasMounted from? ¿Por qué? - Analiza la salida del segundo push. ¿Por qué es instantáneo?
- Verifica con
docker manifest inspectque ambas etiquetas tienen el mismo digest. - Borra todas las copias locales y ejecuta la API descargándola del registro. Comprueba
/saludy el estado del healthcheck. - 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
# 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 -2Ni 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.
TAG IMAGE ID
1 4e9c7d2a8f31
1.1 4e9c7d2a8f31
1.1.0 4e9c7d2a8f31
estable 4e9c7d2a8f31
latest 4e9c7d2a8f31
sha-abc1234 4e9c7d2a8f31Solo 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}}"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 /app9c1e4a7f2b9d: 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: 1785Tres 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.
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 | sha256sumb8e2f4a91c37d5e8a2f6c9b1d4e7a3f8c5b2e9d6a1f4c7b0e3a6d9f2c5b8e1a4 -
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:c4f81b2e9a7d3061f5b8c2e4a9d7f3b1e6c8a2d4f9b7e3c1a5d8f2b6e4c9a7d3Fí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.
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 ./apiEs 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 ./apiSube 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 si1.2.1sale 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 basenode:22-alpinetraiga 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 ./apiEtiqueta 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:
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:
- 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.
- Imposibilita el rollback. Al mover
:producciona 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. - Provoca despliegues heterogéneos. Con
imagePullPolicy: Alwayso 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.2Así, "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
- ¿Qué es Docker?
- Instalando Docker
- Arquitectura de Docker
- Comandos Básicos de Docker
- Entendiendo las Imágenes de Docker
- Creando tu Primer Contenedor Docker
- El Proyecto del Curso: la Plataforma Aurora Libros
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de Dockerfile
- Instrucciones Avanzadas del Dockerfile
- Gestionando Imágenes Docker
- Etiquetado y Publicación de Imágenes
Módulo 3: Contenedores Docker
- Ejecutando Contenedores
- Ciclo de Vida del Contenedor
- Gestionando Contenedores
- Inspección y Depuración de Contenedores
- Redes en Docker
- Persistencia de Datos con Volúmenes
- Límites de Recursos y Políticas de Reinicio
Módulo 4: Docker Compose
- Introducción a Docker Compose
- Definiendo Servicios en Docker Compose
- Comandos de Docker Compose
- Aplicaciones Multi-Contenedor
- Variables de Entorno en Docker Compose
- Perfiles, Overrides y Múltiples Entornos
- Desarrollo Local con Docker Compose
Módulo 5: Conceptos Avanzados de Docker
- Profundización en Redes Docker
- Opciones de Almacenamiento Docker
- Mejores Prácticas de Seguridad en Docker
- Optimizando Imágenes Docker
- Builds Avanzadas con BuildKit y Buildx
- Registro y Monitoreo en Docker
- El Runtime por Dentro: Namespaces, Cgroups y Capas
Módulo 6: Docker en Producción
- Preparar una Imagen para Producción
- CI/CD con Docker
- Orquestando Contenedores con Docker Swarm
- Introducción a Kubernetes
- Desplegando Contenedores Docker en Kubernetes
- Escalado y Balanceo de Carga
- Estrategias de Despliegue y Rollback
