Del código a la imagen
Introducción
Bienvenida
El objetivo de este curso es presentar las herramientas que AWS ofrece para desplegar aplicaciones con buenas prácticas
de integración y despliegue continuos (CI/CD).
Primero definiremos los conceptos que usaremos durante el curso. Luego explicaremos la mecánica del taller y la forma de trabajo para las próximas semanas.
La narrativa del taller
Durante cuatro semanas se desplegará y operará una aplicación web real de principio a fin, sobre infraestructura de AWS. No se trata de ejercicios aislados: cada sesión avanza un paso concreto de la misma historia. Al terminar la Semana 4, se contará con un flujo completo para el despliegue de una aplicación, desde su desarrollo hasta su operación:
CodeCommit → CodeBuild → ECR → ECS → CloudWatch
| Servicio | Rol en el pipeline |
|---|---|
| CodeCommit | Repositorio de código fuente |
| CodeBuild | Construcción de la imagen |
| ECR | Registro de imágenes Docker |
| CloudFormation | Infraestructura como código |
| ECS + Fargate | Ejecución de contenedores |
Semana 1
La primera semana establece los cimientos que el resto del taller supone conocidos. Al terminar la sesión del viernes se contará con:
- Un repositorio de código en CodeCommit, con el código de la aplicación ya cargado.
- Un pipeline de integración continua en CodeBuild que, cada vez que se ejecuta, compila la imagen y la publica en Amazon ECR.
- La aplicación en línea: accesible desde el navegador a través de un Application Load Balancer, desplegada con un template de CloudFormation provisto por el instructor, sobre ECS/Fargate, conectada a una tabla de DynamoDB.
El template de CloudFormation se usa esta semana como una caja negra: se lanza y se obtiene un ambiente funcional en minutos. Cómo está construida por dentro es el tema de la Semana 2.
El mecanismo de recuperación
Desde el primer día se practica destruir el ambiente completo y recrearlo desde cero. Esto no es un ejercicio de destrucción: es el seguro del taller. Si algo sale mal en cualquier sesión posterior (una configuración equivocada, un recurso corrompido) se borra el stack de CloudFormation, se espera a que termine, y se lo vuelve a lanzar con los mismos parámetros, dejándolo en el punto de partida anterior. El costo de un error se reduce a minutos de espera.
Si algo sale mal, borramos el stack de CloudFormation, esperamos a que termine, y lo volvemos a
lanzar con los mismos parámetros. El costo de un error se reduce a minutos de espera.
Cómo usar esta guía
Cada sección combina teoría breve con práctica guiada paso a paso. El esquema es siempre el mismo:
- Teoría (10–15 min): el instructor presenta el problema del día y el servicio de AWS que lo resuelve. El instructor utilizará diapositivas directamente conectadas a esta guía, por lo que se puede seguir al instructor en cualquiera de las dos vistas.
- Práctica guiada: seguir los pasos numerados en la guía. Intentar cada ejercicio antes de revelar la solución.
- Solución oculta: si se producen bloqueos, pulsar el botón Ver solución bajo cada ejercicio. Aparecerán los clics exactos. Al final de cada sesión todos los participantes quedan en el mismo punto.
- Preguntas puente: al terminar la sesión del miércoles (presencial), aparecen dos o tres preguntas que conectan lo construido con lo que viene el viernes (remota). Pensarlas antes de la siguiente sesión.
Requisitos previos
Antes de comenzar, confirmar que se cuenta con lo siguiente:
- Cuenta AWS con permisos para CodeCommit, CodeBuild, ECR, CloudFormation, ECS y Fargate, y DynamoDB. En caso de duda, consultar con el instructor.
- Navegador web actualizado (Chrome o Firefox recomendados).
- Template de CloudFormation
taller-semana1.yaml, también provisto por el instructor. Se utiliza en la sección 4.
Objetivos de la sesión
- Crear el repositorio, clonarlo desde su origen y subir el código a CodeCommit
- Construir la imagen con CodeBuild
- Publicar la imagen en ECR
- Desplegar el template de CloudFormation para el despliegue inicial
Cada ejercicio incluye su solución oculta — botón Ver solución en la guía.
¿Qué es DevOps?
Una combinación de prácticas culturales, servicios y herramientas que aumentan la capacidad de entregar software a alta velocidad.
El objetivo es unificar las áreas de desarrollo y operaciones, las cuales comúnmente suelen estar completamente separadas, para conseguir un mejor flujo desde el código a la aplicación desplegada.
La interfaz entre Desarrollo y Operaciones
Podemos representar la relación entre Desarrollo y Operaciones mediante una analogía con modelos por capas, como OSI
o núcleo/capa exterior (core/shell). Cada equipo concentra ciertas responsabilidades en su núcleo (core), pero
necesita una interfaz exterior (shell) para comunicarse con el otro.
Usaremos esta analogía para identificar los puntos de contacto y analizar las conversaciones que ambos equipos necesitan para mantener un flujo continuo de información y evitar bloqueos.
Operaciones trabaja en la frontera entre la aplicación y su entorno de ejecución; no se limita a ejecutarla. Desarrollo y Operaciones deben definir en conjunto cómo se expone la aplicación a los clientes. Estas decisiones incluyen los protocolos de comunicación, la arquitectura, los chequeos de salud, el cifrado, los controles de seguridad y la conectividad.
Ambos equipos deben participar en estas decisiones desde la definición del artefacto desplegable.
El artefacto desplegable
El artefacto desplegable es la unidad que el pipeline entrega al entorno de ejecución. En el caso más simple, puede ser el propio código fuente, entregado a Operaciones para su despliegue. Este modelo aparece con lenguajes interpretados o de scripting, como Python, Ruby, PHP o JavaScript.
Sin embargo, entregar solo el código fuente deja una pregunta abierta: ¿requiere la aplicación algún proceso de construcción antes del despliegue? El código fuente resulta suficiente cuando el despliegue consiste en copiarlo y ejecutarlo; en los demás casos, el proceso necesita una definición explícita.
Una opción más sólida incorpora un proceso de construcción descrito mediante un script o una tarea, como un
Makefile. Este proceso puede producir un directorio, un archivo tar, un paquete o un binario. El script no es el
artefacto desplegable: define una forma reproducible de generarlo.
Aun así, el resultado de la construcción no siempre describe el despliegue completo. El contrato de despliegue también debe especificar:
- Configuración: argumentos, variables de entorno y secretos.
- Dependencias: runtimes, bibliotecas, paquetes del sistema y servicios externos de red.
Desarrollo y Operaciones deben definir estos requisitos y acordar cómo los consume la aplicación. Muchas decisiones dependen del entorno corporativo, su historia y las aplicaciones que ya están en producción. La solución correcta es la que se ajusta a ese contexto, no necesariamente la más nueva, la más popular o la mejor en términos aislados.
Este punto genera fricción cuando Desarrollo queda fuera de las tareas del día 2 y no participa en la operación de los sistemas en producción, y cuando Operaciones no tiene entrada en el día 1, durante el desarrollo del producto.
Imágenes como artefactos desplegables
Las imágenes de contenedor y de máquina virtual también pueden servir como artefactos desplegables. Herramientas como Packer y, según el flujo de trabajo, Ansible permiten construirlas o configurarlas.
Estas imágenes facilitan la gestión de las dependencias internas de la aplicación. El equipo de Desarrollo puede definirlas y probarlas durante su propio flujo de trabajo en un entorno representativo de producción.
Sin embargo, adoptar imágenes como artefactos desplegables exige la participación de Operaciones en su diseño. Ambos equipos deben acordar las imágenes base, el tamaño, las actualizaciones, las prácticas de seguridad, los puertos expuestos, el tiempo de construcción, el tiempo de arranque y el rendimiento durante la ejecución.
Pipeline de entrega continua (CI/CD)
Busca automatizar el flujo desde los últimos cambios realizados a la aplicación hasta su despliegue en producción, asegurando que cumple con los requisitos básicos de calidad, así como con el contexto necesario para su monitoreo continuo en día 2.
+-------------------------+ +-------------------+
| CI | | CD |
| Código -> Build -> Test | --> | Deploy -> Monitor |
| Day 1 | | Day 2 |
+-------------------------+ +-------------------+
El origen del código — CodeCommit
En julio de 2024 AWS cerró CodeCommit a clientes nuevos: solo las cuentas que ya tenían repositorios podían crear más. Tras el reclamo de los clientes, AWS revirtió la decisión y el 24 de noviembre de 2025 el servicio volvió a estar disponible para todo el mundo. Hoy cualquier cuenta puede crear repositorios desde la consola, la CLI o la API, y se pueden replicar los laboratorios en una cuenta personal.
Pre-requisitos
Completar estos pasos antes de la sesión.
1. Instalar git
Obtener el cliente:
- Windows: descargar e instalar Git for Windows.
- Mac: ejecutar
xcode-select --installen la Terminal, o si se tiene Homebrew:brew install git(git-scm.com/download/mac).
Verificar la instalación:
git --version
2. Configuración mínima
git config --global user.name "Su Nombre"
git config --global user.email su-correo@ejemplo.com
Hay tres vías de acceso. Elegir la que corresponda a la cuenta.
Las opciones 3 y 4 requieren un usuario IAM. Si la organización usa AWS Identity Center (SSO) para iniciar sesión, la identidad es federada y no existe un usuario IAM — esas dos opciones no estarán disponibles. En ese caso, ir directamente a la opción 5.
Existent múltiples formas de acceder a CodeCommit en AWS, las cuales dependen del tipo de autenticación que usen.
3. Acceso HTTPS (cuenta con usuario IAM)
En la consola de IAM → el usuario → pestaña Security credentials → sección
HTTPS Git credentials for AWS CodeCommit → pulsar Generate credentials.
Guardar el usuario y la contraseña generados; se necesitarán al hacer git push.
4. Acceso SSH (cuenta con usuario IAM)
Generar un par de claves si aún no se tiene uno:
ssh-keygen -t rsa -b 4096
Luego, en la consola de IAM → el usuario → Security credentials →
SSH keys for AWS CodeCommit → Upload SSH public key. Copiar el contenido
de ~/.ssh/id_rsa.pub y pegarlo. Anotar el SSH key ID que IAM asigna
(comienza con APKA…).
Configurar ~/.ssh/config:
Host git-codecommit.*.amazonaws.com
User APKA................
IdentityFile ~/.ssh/id_rsa
5. Acceso con IAM Identity Center (SSO)
Si se inicia sesión con AWS IAM Identity Center (antes AWS SSO), la identidad es
federada y no existe un usuario IAM, por lo que las opciones 3 y 4 no aplican.
Usar git-remote-codecommit, que autentica con las credenciales del perfil del
AWS CLI.
-
Instalar AWS CLI v2 (guía oficial).
-
Configurar un perfil con SSO y anotar el nombre del perfil asignado:
aws configure sso -
Instalar el ayudante de git (requiere Python):
pip install git-remote-codecommit -
Con esta vía, las URLs de CodeCommit toman la forma
codecommit::<región>://<perfil>@<nombre-del-repositorio>; no se necesitan credenciales HTTPS ni claves SSH.
Elegir la vía que corresponda a la cuenta. Si se dispone de un usuario IAM, cualquiera de las opciones 3, 4 o 5 funciona; si se usa Identity Center, usar la opción 5.
HTTPS, SSH o Identity Center: ¿cuál elegir?
HTTPS es la más simple de configurar (solo usuario y contraseña generados en
IAM), pero pide credenciales en cada operación salvo que se use un credential helper.
SSH requiere generar y registrar una clave, pero después autentica de forma
transparente. Ambas necesitan un usuario IAM. Si la cuenta usa Identity
Center, no existe usuario IAM: la única vía es git-remote-codecommit (opción 5),
que reutiliza la sesión del AWS CLI. Para el taller, usar la que corresponda a cómo
se inicia sesión en AWS.
El problema del código sin versionar
Imagine que se trabaja en equipo sobre los mismos archivos: ¿cómo saber quién cambió qué y cuándo? ¿Cómo volver al estado de ayer si algo se rompió hoy? ¿Cómo trabajar en una nueva funcionalidad sin afectar el código que ya funciona? Estos son los problemas que el control de versiones resuelve.
Un sistema de control de versiones registra cada cambio en el código como un commit: un punto en el tiempo con un autor, una fecha y un mensaje que describe qué se modificó. El historial completo de commits forma el repositorio. Con él se puede navegar hacia cualquier punto del pasado, comparar estados, y trabajar en paralelo sobre distintas líneas de desarrollo llamadas ramas (branches).
CodeCommit: repositorios Git administrados en AWS
AWS CodeCommit es un servicio de control de versiones compatible con Git, alojado completamente en AWS. No requiere instalar ni operar ningún servidor: se crea el repositorio desde la consola, y AWS se encarga de la disponibilidad, la seguridad, y los respaldos.
En este taller cada participante trabaja sobre su propio repositorio individual. Eso
evita conflictos entre participantes y permite avanzar a ritmo propio. El nombre
del repositorio sigue la convención taller-aws-<su-nombre>, donde <su-nombre> es
el primer nombre en minúsculas y sin acentos (por ejemplo: taller-aws-maria).
Herramientas
1. Code
Permite explorar los archivos y las carpetas de una rama, abrir su contenido y copiar la URL para clonar el repositorio.
2. Pull requests
Permite proponer la fusión de una rama en otra, revisar los cambios y registrar la discusión antes de integrar el trabajo.
3. Commits
Muestra el historial de cambios de la rama seleccionada. Cada commit identifica quién hizo el cambio, cuándo lo realizó y qué archivos modificó.
4. Branches
Lista las líneas de desarrollo del repositorio y permite crear una rama a partir de
otra, por ejemplo dev desde main.
5. Git tags
Permite identificar un commit con un nombre estable, como v1.0.0, para señalar una
versión, una entrega o un punto importante del historial.
6. Settings
Centraliza la configuración del repositorio, como los disparadores (triggers), las reglas de aprobación y las notificaciones. Las opciones disponibles dependen de los permisos de IAM.
Eventos, controles y pipelines
Cada acción relevante en Git —un push, la apertura o actualización de un PR, un
merge o la creación de un tag— puede publicar un evento. Operaciones puede suscribirse
a esos eventos mediante webhooks, triggers o servicios de eventos para iniciar un
pipeline. Así se automatizan tareas como fmt, lint, build, pruebas y despliegues,
pero también controles de seguridad: detectar secretos expuestos, verificar
dependencias y confirmar que las migraciones de base de datos se ejecutan sin errores.
Estos controles pueden intervenir antes de la integración: una regla de aprobación o un pipeline que falla debe impedir el merge hasta que el cambio cumpla los requisitos del equipo. En las próximas secciones configuraremos estas herramientas para convertir el flujo acordado en integración y despliegue continuos.
GitOps
GitOps conecta el flujo de desarrollo con la operación de los ambientes. La idea no es que Operaciones controle cada cambio manualmente, sino acordar cómo los cambios de Git avanzan hasta un despliegue seguro. Para ello, Desarrollo y Operaciones necesitan compartir el significado de las ramas, los pull requests (PRs), las etiquetas y los eventos que desencadenan automatizaciones.
El flujo de desarrollo es un acuerdo
La rama dev del ejemplo anterior es un punto de integración, no una regla universal.
Cada empresa elige un flujo según sus equipos, su infraestructura y los ambientes de
prueba disponibles. Lo importante es que Desarrollo y Operaciones entiendan el mismo
acuerdo: qué representa cada rama, quién aprueba un PR, qué cambio puede ir a cada
ambiente y qué versión está en producción.
Estrategia con ramas por ambiente
Un enfoque frecuente mantiene ramas que representan ambientes: dev, qa y
production. Los desarrolladores crean ramas de trabajo a partir de dev y abren PRs
contra ella. Otros desarrolladores revisan el código, los criterios de aceptación y las
reglas del equipo. Cuando el PR se aprueba y fusiona, el cambio entra en dev.
gitGraph
commit id: "base"
branch dev
checkout dev
commit id: "D1"
branch feature-work
checkout feature-work
commit id: "F1"
commit id: "F2"
checkout dev
merge feature-work id: "PR #42"
branch staging
checkout staging
commit id: "release candidate" tag: "v1.4.0-rc.1"
checkout dev
branch production
checkout production
merge staging id: "PR #43" tag: "v1.4.0"
Cada círculo representa un commit y cada carril, una branch. La rama
feature-work se integra en dev con el merge del PR #42; después, el commit
promovido a staging recibe el tag v1.4.0-rc.1. Finalmente, el PR #43 promueve
esa versión a production y la identifica con el tag v1.4.0.
Luego se promueven los cambios a staging para probar un release candidate y, cuando
están listos para producción, a production. Este modelo hace muy visible qué código
corresponde a cada ambiente. A cambio, hay que evitar que las ramas se alejen entre sí
y definir con cuidado cómo se propagan las correcciones urgentes.
Estrategia de trunk-based development
Otra alternativa es trabajar con una única rama principal, o trunk, normalmente
main. Cada desarrollador abre un PR contra main y, al fusionarlo, su punta se
considera código listo para desplegar en desarrollo. Para probar una versión en
staging se puede crear una rama de release, o se puede marcar el commit con un tag,
por ejemplo v1.4.0. Ese tag identifica exactamente qué versión se está promoviendo y
puede ser la entrada del despliegue a producción.
gitGraph
commit id: "base"
commit id: "M1"
branch feature-search
checkout feature-search
commit id: "F1"
commit id: "F2"
checkout main
merge feature-search id: "PR #51"
commit id: "M2"
branch release-1-4
checkout release-1-4
commit id: "release candidate" tag: "v1.4.0-rc.1"
checkout main
merge release-1-4 id: "PR #52" tag: "v1.4.0"
En este flujo, main es el trunk: las ramas de funcionalidad viven poco tiempo y
se integran mediante PRs. La rama release-1-4 es opcional; permite validar un
candidato antes de su merge. El tag v1.4.0 señala el commit exacto que se puede
promover a producción.
Ambientes efímeros por PR
Con más cambios producidos por asistentes y agentes de código, es especialmente útil validar antes de fusionar. Un patrón cada vez más usado crea un ambiente efímero por PR: un sandbox que emula lo necesario del ambiente productivo para probar esa funcionalidad sin afectar a los demás equipos. Al fusionar o cerrar el PR, la automatización elimina los recursos. Esto reduce el riesgo de probar cambios aislados y da a Desarrollo, QA y Operaciones una evidencia compartida antes del merge.
gitGraph
commit id: "base"
branch feature-search
checkout feature-search
commit id: "S1"
commit id: "preview PR #57 listo"
checkout main
branch feature-profile
checkout feature-profile
commit id: "P1"
commit id: "preview PR #58 listo"
checkout main
merge feature-search id: "PR #57"
merge feature-profile id: "PR #58"
Las dos ramas de funcionalidad y sus PRs existen en paralelo. Cada commit de
preview ... listo representa que su pipeline creó un ambiente efímero independiente
para ese PR y superó las validaciones. Al fusionar o cerrar el PR, la automatización
elimina los recursos de su ambiente; no son ramas ni tags permanentes.
Gestión de datos entre ambientes
Gestión de datos
Los datos y las dependencias externas determinan qué tan fiel puede ser cada ambiente de prueba, especialmente cuando el ambiente nace para un PR y desaparece al terminar.
Cuando contamos con ambientes persistentes, la información puede crecer de forma orgánica, ser administrada por un equipo de datos o provenir de una sincronización controlada desde producción. Con procedimientos repetibles.
En los dos enfoques con ambientes persistentes, cada ambiente suele tener su propia infraestructura y sus propias bases de datos. La información puede crecer de forma orgánica, ser administrada por un equipo de datos o provenir de una sincronización controlada desde producción. Con procedimientos repetibles —migraciones, seeds, enmascaramiento y restauraciones— es posible mantener datos ricos, válidos y seguros para Desarrollo y QA.
La práctica requiere proteger la información de producción: nunca se deben copiar secretos ni datos personales sin las políticas, permisos y transformaciones adecuadas. Los datos sintéticos, anonimizados o seleccionados para cada caso de prueba suelen ser la opción más segura y fácil de reproducir.
En los ambientes efímeros el desafío cambia. La infraestructura se crea de forma dinámica para cada PR, por lo que también se debe producir dinámicamente una fuente de datos útil. La factibilidad del patrón depende, en gran medida, de qué tan fácil sea crear un equivalente de las fuentes de datos productivas.
Si la aplicación solo necesita una base SQL, puede bastar con SQLite, un contenedor de base de datos o un respaldo periódico restaurado al crear el ambiente. A medida que el sistema incorpora varias bases de datos, colas, índices, archivos y servicios externos, la preparación de datos y dependencias se vuelve más costosa. Para servicios de terceros, a menudo se usan sandboxes, simuladores (mocks) o contratos de prueba.
Por eso, antes de adoptar ambientes efímeros, Desarrollo, QA, Datos y Operaciones deben acordar qué datos necesita cada prueba, cómo se generan, cuánto tardan en prepararse y qué dependencias se pueden emular. Esa conversación define si el ambiente será una validación confiable o solo una aproximación superficial.
¿Existe una mejor opción?
No existe un flujo de ramas superior en todos los contextos. La decisión no debería responder a una preferencia personal, sino al riesgo, la frecuencia de despliegue, la madurez de las pruebas, los requisitos de auditoría y los ambientes disponibles.
Las ramas por ambiente hacen explícita la promoción entre dev, staging y
production, pero exigen mantenerlas sincronizadas. El trunk reduce esa divergencia
y acelera la integración, pero requiere cambios pequeños, pruebas confiables y una
disciplina de despliegue frecuente.
Elegir un modelo es acordar un lenguaje común: qué representa cada rama, qué controles debe pasar un PR, cómo se identifica una versión y cuándo se despliega. Un flujo simple, visible y entendido por Desarrollo y Operaciones es más valioso que seguir una receta popular que no encaja con la organización.
Práctica guiada: crear el repositorio y subir el código
CodeCommit
Consola
- Iniciar sesión en la consola de AWS en console.aws.amazon.com.
- Abrir CodeCommit.
- Confirmar que la región seleccionada (esquina superior derecha) es la misma que indicó el instructor. El taller usa una única región para todos los recursos.
Usaremos la región us-east-2.
Crear el repositorio
- Pulsar Create repository.
- En Repository name, escribir
taller-aws-<su-nombre>. Usar el primer nombre en minúsculas y sin acentos (por ejemplo:taller-aws-carlos). - En Description (opcional), escribir una descripción breve, por ejemplo:
Repositorio del taller AWS DevOps — Semana 1. - Dejar las demás opciones con sus valores predeterminados y pulsar Create.
CodeCommit crea el repositorio vacío en segundos y lleva a la vista principal del repositorio.
Clonar el código desde GitHub
Los comandos de git que se usarán
git clone <url>— copia un repositorio remoto completo a la máquina, con todo su historial.git remote -v— lista los remotos configurados y sus URLs.git remote add <nombre> <url>— registra un remoto adicional bajo un nombre.git push -u <remoto> <rama>— sube los commits de una rama al remoto, y con-urecuerda la asociación para futurosgit push.git status— muestra el estado del directorio de trabajo.git log— muestra el historial de commits.
Clonar el repositorio del taller desde GitHub y entrar al directorio:
git clone https://github.com/cloudbridgeuy/courses
cd courses
Conectar el repositorio de CodeCommit
En la vista del repositorio recién creado en la consola, pulsar el botón Clone URL y copiar la URL correspondiente al método de acceso configurado:
- HTTPS:
https://git-codecommit.<región>.amazonaws.com/v1/repos/taller-aws-<su-nombre> - SSH:
ssh://git-codecommit.<región>.amazonaws.com/v1/repos/taller-aws-<su-nombre> - Identity Center (grc):
codecommit::<región>://<perfil>@taller-aws-<su-nombre>(no aparece en el botón Clone URL; construirla con la región y el nombre del perfil).
Agregar CodeCommit como remoto adicional:
git remote add codecommit <url-copiada>
Verificar que ambos remotos estén registrados:
git remote -v
Debe verse origin (GitHub) y codecommit.
¿Qué es un repositorio remoto?
Un remoto es una copia del repositorio alojada en otro servidor. origin es solo
el nombre convencional del remoto desde el que se clonó. Un mismo repositorio local
puede tener varios remotos: aquí GitHub queda como fuente de lectura (origin) y
CodeCommit como destino de trabajo (codecommit).
Subir el código
Subir la rama main a CodeCommit:
git push -u codecommit main
Con HTTPS, git pedirá el usuario y la contraseña generados en IAM. Con SSH, la
autenticación es transparente gracias al archivo ~/.ssh/config.
Explorar la vista del repositorio
- Una vez subido el código, navegar por las secciones de la vista del repositorio:
- Code: muestra los archivos y carpetas. Pulsar cualquier archivo para ver su contenido.
- Pull requests: reúne las propuestas de fusión entre ramas y su revisión.
- Commits: muestra el historial. Pulsar un commit para ver exactamente qué cambió.
- Branches: muestra las ramas existentes. Por ahora solo existe
main. - Git tags: muestra las etiquetas que marcan versiones o commits importantes.
- Settings: contiene la configuración y las automatizaciones del repositorio.
4. Branches: la rama dev
Una rama (branch) es una línea paralela de desarrollo. Los cambios en una rama no
afectan a las demás hasta que se fusionan explícitamente. La convención habitual es
mantener main siempre con código funcional y trabajar los cambios en ramas
separadas antes de incorporarlos.
Crear la rama dev desde la consola
- En la pestaña Branches, pulsar Create branch.
- En Branch name, escribir
dev. - En Branch from, seleccionar
main(la rama de la que derivará). - Pulsar Create branch.
La rama dev aparece ahora en la lista. Comparte todos los commits de main
en este momento —es una copia exacta del estado actual.
2. Pull requests: revisar e integrar cambios
Un pull request propone incorporar los cambios de una rama de origen en una rama de
destino. Por ejemplo, al terminar un cambio en dev, se puede abrir un pull request
contra main. La pantalla muestra los commits y las diferencias de archivos, permite
dejar comentarios y, si se cumplen las reglas configuradas, fusionar las ramas.
Para crear uno desde la consola:
- Abrir Pull requests y pulsar Create pull request.
- Elegir la rama de origen, por ejemplo
dev, y la rama de destino, por ejemplomain. - Escribir un título y una descripción que expliquen el cambio; revisar la pestaña de diferencias (Changes).
- Crear el pull request. Tras la revisión, pulsar Merge para integrar los cambios en la rama de destino.
En un repositorio de equipo, la revisión y las aprobaciones ayudan a proteger main.
En este taller el pull request ilustra el flujo, aunque cada participante trabaje en
su propio repositorio.
5. Git tags: marcar una versión
Una etiqueta Git (tag) asigna un nombre legible a un commit concreto. A diferencia de
una rama, no es una línea de trabajo: sirve para conservar una referencia a una versión
o entrega, por ejemplo v1.0.0.
Desde Git tags, pulsar Create Git tag, indicar el nombre de la etiqueta y seleccionar el commit que se quiere marcar. Antes de crearla, confirmar que el commit corresponde a la versión que se desea conservar; luego la etiqueta aparecerá en la lista y en el historial del repositorio.
6. Settings: configurar el repositorio
Abrir Settings para consultar y administrar las opciones del repositorio. Allí se pueden configurar disparadores que reaccionan a eventos de Git, plantillas de reglas de aprobación para pull requests y notificaciones de actividad. Usar estas opciones cuando el repositorio necesite automatizar una acción o establecer revisiones antes de fusionar cambios.
No todas las cuentas pueden cambiar la configuración. Si una opción no aparece o AWS deniega la acción, solicitar al administrador los permisos de CodeCommit necesarios.
3. Commits: localizar el ID del commit
- Pulsar sobre el nombre de la rama
devpara abrirla. - En la pestaña Commits, se verá el historial. El commit ID es el identificador
hexadecimal largo que aparece junto a cada commit (por ejemplo:
a1b2c3d4e5f6...). Copiar los primeros 8 caracteres —son suficientes para identificar un commit de forma única en este repositorio.
Ejercicio 1 — Clonar, conectar y subir el código
Clonar el repositorio https://github.com/cloudbridgeuy/courses desde GitHub, crear
un repositorio de CodeCommit llamado taller-aws-<su-nombre>, agregarlo como
remoto, y subir la rama main.
-
Clonar el código y entrar al directorio:
git clone https://github.com/cloudbridgeuy/courses cd courses -
Abrir CodeCommit, pulsar Create repository, y crear
taller-aws-<su-nombre>(el primer nombre en minúsculas, sin acentos). -
Agregar CodeCommit como remoto, según el acceso configurado en los pre-requisitos:
# Variante HTTPS (usuario IAM) — copiar la Clone URL del repositorio git remote add codecommit https://git-codecommit.<región>.amazonaws.com/v1/repos/taller-aws-<su-nombre> # Variante SSH (usuario IAM) — copiar la Clone URL del repositorio git remote add codecommit ssh://git-codecommit.<región>.amazonaws.com/v1/repos/taller-aws-<su-nombre> # Variante Identity Center (grc) — construirla con la región y el perfil git remote add codecommit codecommit::<región>://<perfil>@taller-aws-<su-nombre> -
Verificar los remotos con
git remote -v— deben verseorigin(GitHub) ycodecommit. -
Subir la rama
main:git push -u codecommit main -
En la consola de CodeCommit, abrir el repositorio: los archivos aparecen en la pestaña Code.
Puede ser necesario que tengan que configurar el perfil que van a utilizar
con la variable de entorno AWS_PROFILE.
Con HTTPS, git pedirá el usuario y la contraseña generados en IAM. Con
Identity Center (grc), se reutiliza la sesión activa de la awscli.
Ejercicio 2 — Crear una rama y encontrar su commit
Desde la consola de CodeCommit, crear la rama dev a partir de main. Luego
localizar el ID del commit más reciente en esa rama.
- En la vista del repositorio, pulsar la pestaña Branches.
- Pulsar Create branch.
- En Branch name, escribir
dev. - En Branch from, seleccionar
main. - Pulsar Create branch. La rama aparece en la lista.
- Pulsar sobre el nombre
devpara abrirla. - Seleccionar la pestaña Commits. El commit más reciente aparece al tope de la lista.
- El commit ID es el identificador hexadecimal largo junto al commit. Los primeros 8 caracteres son suficientes para identificarlo de forma única. Anotarlos — se usarán como referencia en la Semana 2.
Integración continua — CodeBuild, y ECR
Problema: ¿Como construir de forma reproducible?
El código fuente no se despliega directamente: primero se transforma en un
artefacto desplegable. En este ejemplo, el artefacto es una imagen de contenedor
construida a partir del Dockerfile en la raíz del repositorio.
Construirla en la máquina de cada desarrollador parece sencillo, pero incorpora
diferencias difíciles de detectar: sistema operativo, arquitectura del procesador,
versiones de herramientas y dependencias instaladas. Docker reduce esas diferencias,
pero no las elimina. Por ejemplo, una imagen creada en macOS sobre ARM, sin indicar
--platform linux/amd64, puede no ejecutarse en un servidor x86.
La integración continua ejecuta el build en un entorno estándar, limpio y reproducible. Así, cada commit o tag produce un artefacto cuyo origen y proceso de construcción se pueden verificar, sin depender de una máquina local.
Respuesta en AWS: CodeBuild y Amazon ECR
AWS CodeBuild es un servicio de build totalmente administrado. Se le indica la
fuente, la imagen del entorno de construcción y los comandos que debe ejecutar.
CodeBuild aprovisiona un entorno limpio, ejecuta el trabajo y libera los recursos al
terminar. No hay servidores de build que mantener.
¿Qué expresa un buildspec.yml?
Un buildspec.yml es una especificación YAML que funciona como el pequeño DSL de
CodeBuild: declara las fases del trabajo y los comandos que se ejecutan en cada una.
version: 0.2
phases:
install:
commands:
- npm ci
build:
commands:
- npm test
- docker build -t app:$IMAGE_TAG .
versionselecciona la versión del formato de buildspec.phasesorganiza el proceso; las fases permitidas son:install,pre_build,buildypost_build.commandscontiene la lista de órdenes de shell de cada fase, ejecutadas en orden.- También puede declarar
env,artifacts,cacheyreports.
¿Qué es Amazon ECR?
Amazon Elastic Container Registry (ECR) es el registro de imágenes de contenedores de AWS. Como DockerHub
Una vez construida la imagen Docker, hay que guardarla en algún lugar. Amazon ECR (Elastic Container Registry) es el servicio de AWS para almacenar imágenes Docker de forma privada y segura. Cuando ECS necesite lanzar el contenedor en la Semana 3, irá a buscar la imagen directamente a ECR.
Artefactos desplegables: construir una vez, promover muchas veces
El resultado de CodeBuild no es solo una imagen Docker: es el artefacto desplegable que conecta el cambio de código con un despliegue. Debe poder responderse con precisión qué commit, qué PR o qué tag de Git produjo una imagen, qué validaciones superó y en qué ambientes se utilizó.
En un pipeline de equipo, un evento de Git inicia el trabajo adecuado: un PR puede
ejecutar lint, pruebas, análisis de seguridad y un ambiente efímero; un merge o un
tag de release puede construir la imagen candidata. Cuando esa imagen se aprueba para
otro ambiente, se promueve el mismo artefacto, sin reconstruirlo. Así, staging y
producción prueban y ejecutan exactamente los mismos bytes.
La realidad es que no siempre se sigue esta práctica, y lo que termina pasando es que se termina reconstruyendo el artefacto en cada fase de su ciclo de vida. Algunas veces hay alguna razón para hacerlo, pero otras veces es simplemente un error.
Lo ideal, es que promovamos un mismo artefacto a través de distintos ambientes. Si es necesario que se comporte distinto en cada ambiente (se conecte a distintos servicios de terceros, bases de datos, nivel de logging, etc.) estos cambios de comportamiento deben producirse a través de cambios en su configuración, las cuales puede absorber de diversas maneras: variables de entorno, argumentos, archivos de configuración, etc.
Para lograrlo, conviene identificar las imágenes con una referencia inmutable o
inequívoca: el SHA del commit, un tag de versión como v1.4.0, o el digest que ECR
asigna a la imagen. Una etiqueta mutable como latest es cómoda para un laboratorio,
pero no indica qué versión se está desplegando y puede cambiar entre dos operaciones.
Nuestro archivo buildspec.yml
El archivo buildspec.yml vive en la raíz del repositorio que se clonó y subió a
CodeCommit en la sección anterior, junto al Dockerfile. Es el contrato entre el
código y CodeBuild, y usa las cuatro fases:
De más está decir que cada command puede invocar cualquier herramienta o script dentro
del ambiente de CodeBuild, o dentro del repositorio. Los commands se ejecutan desde
un directorio con la revisión de código que disparó el proceso de build.
Es común utilizar comandos de bash directamente, pero no es necesario. Podemos, por
ejemplo, ejecutar un script de python almacenado dentro del repositorio:
version: 0.2
phases:
install:
runtime-versions:
python: 3.12
build:
commands:
- python --version
- python -m pip install -r requirements.txt
- python scripts/build_image_metadata.py
Por defecto, el archivo buildspec.yml es leído por CodeBuild desde la revisión que lanzó
el build. Esto tiene la ventaja de que podemos probar cambios en el buildspec.yml sin
necesidad de desplegar nada.
Por otro lado, podemos fijarlo si seleccionamos una ruta alternativa como fuente, o
mediante la opción buildSpecOverride al momento de iniciar el build. Colocar una fuente
externa para el buildspec.yml puede ser útil si contamos con un repositorio centralizado
donde gestionamos las especificaciones de ejecución.
En install se confirma que las herramientas del entorno están disponibles. Aquí no
hay nada que instalar porque la imagen administrada de CodeBuild ya trae Docker y la
CLI de AWS. En pre_build se corre hadolint sobre el Dockerfile (volvemos sobre
esto más adelante), se autentica con ECR para poder empujar la imagen construida
al repositorio correcto, y se crea un
builder de BuildKit con docker buildx create --use: el driver por defecto de
Docker no puede exportar cache hacia un registro, y este paso lo habilita. El porqué
del cache lo vemos al final de la sección Cache de build en
CodeBuild.
Es muy común que utilicemos una cuenta para centralizar todas las imágenes de la empresa, desde la cual luego se consumen en los demás ambientes.
Las cuatro fases y su propósito:
| Fase | Propósito |
|---|---|
install | Preparación del entorno: instalar o verificar las herramientas del build. |
pre_build | Preparación del trabajo: lint del Dockerfile, autenticación con ECR y creación del builder. |
build | Construcción y publicación: docker buildx build etiqueta la imagen con el URI de ECR y la empuja (--push), junto con su cache. |
post_build | Verificación: confirmar con aws ecr describe-images que la imagen quedó publicada. |
Las variables de entorno ($AWS_ACCOUNT_ID, $AWS_DEFAULT_REGION, $IMAGE_REPO_NAME,
$IMAGE_TAG) se definen en el proyecto de CodeBuild, no en el archivo. Esto permite
reutilizar el mismo buildspec.yml en distintos entornos sin modificarlo.
SOURCE_BRANCH y CODECOMMIT_REPOSITORY_ARN son opcionales: un pipeline puede
proporcionarlos explícitamente para mantener la trazabilidad incluso cuando el origen
llega como un commit en lugar de una rama.
¿Por qué estos labels?
Los tags permiten seleccionar una imagen al publicarla; los labels guardan dentro de la imagen datos de procedencia que ayudan a reconstruir su historia. Docker los almacena como metadatos y pueden inspeccionarse sin cambiar el contenido del artefacto.
El prefijo org.opencontainers.image es parte de las annotations de OCI.
OCI (Open Container Initiative) define especificaciones abiertas para que las
imágenes de contenedor puedan ser creadas, almacenadas y ejecutadas por herramientas
distintas. En particular, org.opencontainers.image.revision identifica la revisión
del control de código y org.opencontainers.image.source la URL de su fuente. Usamos
esas claves estándar para asociar la imagen con el SHA exacto y el repositorio desde el
que se construyó.
Los labels que empiezan por com.amazonaws son metadatos propios: usan un namespace
de AWS para no chocar con las claves OCI ni con las de otras herramientas. Las
variables de entorno de CodeBuild proporcionan los valores para
seguir el recorrido de la imagen:
| Label | Valor | Qué permite identificar |
|---|---|---|
org.opencontainers.image.revision | GIT_SHA | El commit exacto incluido en la imagen. |
org.opencontainers.image.source | CODEBUILD_SOURCE_REPO_URL | El repositorio desde el que se obtuvo el código. |
com.amazonaws.codebuild.build-arn | CODEBUILD_BUILD_ARN | La ejecución concreta que construyó la imagen. |
com.amazonaws.codebuild.project-arn | CODEBUILD_PROJECT_ARN | El proyecto y su configuración de build. |
com.amazonaws.codebuild.initiator | CODEBUILD_INITIATOR | Quién o qué inició la ejecución, por ejemplo un pipeline. |
com.amazonaws.codecommit.repository-arn | CODECOMMIT_REPOSITORY_ARN | El recurso de CodeCommit que originó el flujo. |
CODECOMMIT_REPOSITORY_ARN es una variable definida por el proyecto o por el pipeline;
CodeBuild no la genera automáticamente. Esta combinación permite partir de una imagen
en ECR y responder qué commit, repositorio y ejecución la produjeron.
Nuestro archivo Dockerfile
El Dockerfile es la otra mitad del contrato: el buildspec.yml declara cuándo y
con qué contexto se construye; el Dockerfile declara cómo. El nuestro también
vive en la raíz del repositorio:
Beneficios, y sus trampas
Lo mejor de trabajar con un Dockerfile es que nos garantiza un artefacto
desplegable que corre igual en la máquina de un desarrollador que en un ambiente
productivo: mismos bytes, mismas dependencias, mismo comportamiento.
Sin embargo, es muy común caer en la realización de múltiples build durante los
flujos de despliegue, desaprovechando esta oportunidad. Cada build repetido cuesta
tiempo de pipeline, y es una chance de producir bytes distintos: una
dependencia que cambió de versión, una imagen base que se actualizó entre un build y
el siguiente. Terminamos desplegando en producción algo que no es lo que probamos.
Una forma de remediar esto es mediante la buena utilización de etiquetas. Tanto las
que definimos nosotros, como los tags y labels de la sección anterior, asi como la información
que expone AWS: el digest que asigna ECR, los ARNs de CodeBuild. La idea es
reutilizar estas fuentes de información para evitar realizar el proceso de build
nuevamente, y en cambio re-etiquetar la imagen correcta a medida que nos movemos
dentro del ciclo de vida del despliegue: la imagen que pasó las pruebas del PR es la
que recibe el tag de staging, y esa misma es la que recibe el tag de release.
Re-etiquetar no requiere descargar ni volver a subir la imagen. ECR permite agregar un tag a una imagen existente manipulando solo su manifiesto:
MANIFEST=$(aws ecr batch-get-image --repository-name "$IMAGE_REPO_NAME" \
--image-ids imageTag="$GIT_SHA" --query 'images[0].imageManifest' --output text)
aws ecr put-image --repository-name "$IMAGE_REPO_NAME" \
--image-tag "v1.4.0" --image-manifest "$MANIFEST"
La operación tarda segundos, sin importar el tamaño de la imagen.
En repositorios donde el resultado es una única imagen, esto es relativamente fácil: hay un solo artefacto que seguir, y su historia es la historia del repositorio. El problema ocurre cuando trabajamos con un mono-repositorio, en donde se gestionan varias aplicaciones que pueden ser desplegadas en distinto orden, a través de diferentes PR. Evitar múltiples builds en estos escenarios, garantizando a la vez que todas las aplicaciones se prueben y ejecuten junto a los demás servicios, es difícil.
Build único en un mono-repositorio de microservicios
Supongamos un mono-repositorio con tres servicios: api (el servidor web que oficia
de API), payments (la plataforma de pago) y gateway (centraliza autenticación y
autorización, y envía los requests permitidos a alguno de los servicios
downstream). Por política de la empresa, en cada push a un PR es necesario
garantizar que los tres servicios pasan por el build de forma exitosa,
independientemente de si sufrieron cambios. Además, el sistema trabaja con ambientes
efímeros: cada PR despliega las versiones compiladas de los tres servicios.
La clave es dejar de identificar las imágenes por el commit del repositorio y pasar a identificarlas por el contenido del servicio. Git ya calcula esta identidad por nosotros: cada directorio tiene un tree hash que solo cambia si cambia algo dentro de él.
Como obtener el tree-hash
❯ TREE_SHA=$(git rev-parse "HEAD:crates/server" | cut -c1-12)
❯ echo $TREE_SHA
303776a4ed6a
❯ git rev-parse "HEAD:crates/server" "HEAD:crates" "HEAD:crates/apps"
303776a4ed6ace5e887a17c3f464607601929bfb
5d43ec0a9ceb0cc60fe160f0e501beafb88567fc
b4067697c8594c8cdce9e37e4fc8e4512a837853
❯ TREE_SHA=$(git rev-parse "HEAD:crates/server" "HEAD:crates" "HEAD:crates/apps" \
| git hash-object --stdin \
| cut -c1-12)
❯ echo $TREE_SHA
78eb2d011698
Con esa identidad, el flujo en cada push al PR es, para cada servicio:
- Calcular su
TREE_SHA(si el servicio depende de código compartido, por ejemplo librerías enlib/, el hash debe combinar ambos árboles — de lo contrario un cambio en la librería pasaría inadvertido). - Preguntarle a ECR si ya existe una imagen con el tag
tree-$TREE_SHA. - Si existe, el build ya ocurrió —y ya fue exitoso—: solo se re-etiqueta esa imagen
con el tag del PR (
pr-123-abc123), en segundos. - Si no existe, se construye, se publica con ambos tags, y queda disponible para el
próximo
push.
En el caso de que aun así, por SOC2 o cualquier otra política, fuese obligatorio
correr el proceso de build, este se realiza, pero no se publican cambios en
el repositorio remoto. De más está decir que si no se siguen buenas prácticas,
algunas de ellas mencionadas a continuación, es posible que estos builds fallen,
lo cual puede producir información valiosa o falsos positivos difíciles de
investigar.
if aws ecr describe-images --repository-name "$SVC" \
--image-ids imageTag="tree-$TREE_SHA" >/dev/null 2>&1; then
MANIFEST=$(aws ecr batch-get-image --repository-name "$SVC" \
--image-ids imageTag="tree-$TREE_SHA" \
--query 'images[0].imageManifest' --output text)
aws ecr put-image --repository-name "$SVC" \
--image-tag "pr-$PR_NUMBER-$GIT_SHA_SHORT" --image-manifest "$MANIFEST"
else
docker build -f "services/$SVC/Dockerfile" \
-t "$IMAGE_URI:tree-$TREE_SHA" \
-t "$IMAGE_URI:pr-$PR_NUMBER-$GIT_SHA_SHORT" .
docker push --all-tags "$IMAGE_URI"
fi
Así se cumple la política: cada push termina con tres imágenes etiquetadas con la
identidad del PR. Algunas recién construidas, otras re-etiquetadas. Y el ambiente
efímero despliega exactamente ese conjunto. La integración se prueba con las tres
versiones reales, pero solo se paga el costo de build de lo que efectivamente cambió.
Al momento del merge a main, el mismo mecanismo decide qué se despliega a
producción: se recalculan los tree hashes y se comparan contra los que producción
está ejecutando (que quedaron registrados como label de la imagen, o en un Parameter
Store). Solo los servicios cuyo hash cambió se promueven —otra vez por re-etiquetado,
nunca por rebuild. Si el cambio fue únicamente en el proceso de autenticación, solo
el tree hash del gateway difiere, y solo el gateway se despliega en producción.
Una advertencia: el tree hash solo ve el contenido commiteado de esa ruta. Si el
Dockerfile de un servicio consume archivos fuera de su directorio (un lockfile en
la raíz, un Dockerfile compartido), esos paths deben formar parte del hash.
Supongamos que los tres servicios comparten librerías en lib/, y que el build
consume el lockfile de la raíz. git rev-parse acepta varias rutas a la vez y
devuelve un hash por línea; combinamos esas líneas en una única identidad
volviéndolas a pasar por git hash-object:
TREE_SHA=$(git rev-parse "HEAD:services/gateway" "HEAD:lib" "HEAD:Cargo.lock" \
| git hash-object --stdin | cut -c1-12)
Si cambia cualquiera de las tres rutas (el código del gateway, una librería en
lib/, o el lockfile cambia alguna de las líneas) cambia con ella el hash combinado.
El cut -c1-12 solo lo acorta para que el tag resulte legible.
El Dockerfile no es solo de los desarrolladores
Es importante que el equipo de DevOps esté involucrado en el proceso de escribir los
Dockerfiles. Son archivos muy flexibles, y es común que los desarrolladores se
preocupen solamente de que su aplicación corra, sin tener en cuenta requerimientos
que maximicen recursos y apliquen buenas prácticas de seguridad.
El primer ejemplo es el uso de multi-stage builds, como en nuestro Dockerfile:
la primera etapa compila el binario con toda la toolchain de Rust (compilador,
cmake, build-essential); la segunda parte de una imagen mínima y copia solamente
el binario y los certificados para TLS. La imagen final solo cuenta con lo mínimo
necesario para ejecutar: se reduce la superficie de ataque —nada de compiladores ni
herramientas de build en producción— y el espacio de almacenamiento.
Lo segundo es la forma en que se descargan las dependencias. No es lo mismo depender de un PPA público que utilizar el Artifact Registry de la compañía. Un registro interno evita exponer dependencias con vulnerabilidades conocidas, y elimina la dependencia de terceros para poder realizar los builds: si el mirror público está caído, el pipeline sigue funcionando.
Un registro de artefactos administrado
Mantener un Artifact Registry interno tampoco es una tarea sencilla, pero de realizarlo correctamente, es algo muy poderoso. AWS nos puede ayudar con la gestión.
- AWS CodeArtifact: registry interno administrado: npm, PyPI, Maven, NuGet, Cargo
- External connections: el paquete se descarga del upstream público una sola vez, y queda retenido
- ECR pull-through cache: lo mismo para imágenes base: Docker Hub, ECR Public, GHCR
- Los paquetes de sistema (
apt) quedan fuera — para eso, la imagen base corporativa
Lo mismo aplica a las imágenes base. Es muy usual depender de imágenes públicas
ubicadas en Docker Hub e inmediatamente tener que lidiar
con problemas de rate limiting en CI: los límites de descarga se comparten por IP,
y los runners de CI los agotan rápido. Además, usualmente apuntamos las etiquetas a
latest, perdiendo el control de qué estamos introduciendo cada vez que hacemos un
nuevo build. Es mejor utilizar la galería pública de ECR,
que no impone los límites agresivos de Docker Hub. O, mejor aún, nuestro propio
repositorio: con un pull-through cache logramos ambas cosas a la vez, y es
exactamente lo que configuramos en el
Ejercicio 6.
En cualquier caso, conviene fijar las imágenes base por su digest y no solamente por etiqueta, en pos de asegurar que siempre hacemos el build con la misma imagen base:
FROM rust:1.95-slim-bookworm@sha256:d7482085ff5b415f84dba5647ae71606650bdef00db7aeb69f4b3d170c3e4082 AS builder
Los dos tipos de pin van juntos. Fijar las versiones de los paquetes
(apt-get install build-essential=12.9) sin fijar el SHA de la imagen base es una
invitación al desastre: es común que la imagen base avance, sus mirrors dejen de
servir las versiones fijadas, y el build (que ayer funcionaba) falle hoy sin que
nadie haya tocado el repositorio. Lo mismo aplica a los repositorios de dependencias
externos: un PPA o mirror público puede dejar de servir una versión en cualquier
momento. Si fijamos versiones, fijamos también la imagen base por su digest o
asumimos que ambos se actualizan juntos, en el mismo commit.
Por último, una buena práctica es contar con una imagen base de uso general para
la organización: una sola imagen, curada por DevOps, que ya incluye los certificados,
la configuración del registro interno y las herramientas comunes. Puede tenerse
pre-cargada en los sistemas de CI para evitar los tiempos de pull, y concentra
todos los beneficios anteriores en un único lugar que se audita y actualiza de forma
centralizada.
Automatizar estas prácticas con hadolint
No hace falta que todas estas reglas vivan solo en la memoria del equipo:
hadolint es un linter de Dockerfiles que
las codifica como reglas verificables. Cada una tiene un código; por ejemplo,
nuestro Dockerfile sin los pins de versión dispara esta:
Dockerfile:5 DL3008 warning: Pin versions in apt get install. Instead of
`apt-get install <package>` use `apt-get install <package>=<version>`
También detecta imágenes base sin fijar (DL3006, DL3007 para latest), capas de
apt sin limpiar (DL3009), sudo innecesario (DL3004), y decenas de reglas más —
incluyendo las de ShellCheck sobre el shell de cada
RUN.
Conviene usarlo en dos lugares:
-
En el editor, para que el desarrollador vea las advertencias mientras escribe. Existen integraciones como extensión del IDE (VS Code) o como servidor LSP / fuente de diagnósticos en Neovim y otros editores, de la misma forma en que
clippynos acompaña en Rust. -
Como tarea de CI, para que la regla sea un contrato y no una sugerencia. En CodeBuild alcanza con una fase:
pre_build: commands: - docker run --rm -i hadolint/hadolint < DockerfileEl comando termina con código distinto de cero si hay advertencias, y el build falla antes de construir nada.
Cuando una regla no aplica, la excepción también queda documentada: un comentario
# hadolint ignore=DL3008 sobre la línea, o un archivo .hadolint.yaml con las
reglas ignoradas a nivel repositorio. Lo importante es que ignorar una regla sea una
decisión visible en el código, revisable en un PR — no una omisión silenciosa.
Cache de build en CodeBuild
En el laboratorio, el primer build tarda entre 10 y 20 minutos porque compila el proyecto desde cero. CodeBuild ofrece varias capas de cache para que los siguientes no paguen ese precio:
-
Cache local: en la configuración del proyecto (Artifacts → Additional configuration → Cache) se puede activar
Local, con el modoDockerLayerCachepara reutilizar las capas de Docker del build anterior. Es la opción más simple, pero es best effort: solo funciona si el build cae en el mismo host que el anterior, algo frecuente con builds seguidos y raro con builds espaciados. -
Cache en S3: la sección
cachedelbuildspec.ymldeclara rutas que CodeBuild guarda y restaura desde S3 entre builds. Sirve para directorios de dependencias (~/.cargo,node_modules), aunque subir y bajar el cache también toma tiempo. -
Cache en un registro (externo): BuildKit puede publicar el cache de capas como un artefacto más dentro de ECR, y consumirlo desde cualquier host —incluso desde la máquina de un desarrollador. Es lo que hace nuestro
buildspec.yml:build: commands: - | docker buildx build \ --cache-from type=registry,ref=$IMAGE_URI:cache \ --cache-to type=registry,ref=$IMAGE_URI:cache,mode=max,image-manifest=true,oci-mediatypes=true \ -t $IMAGE_URI:$IMAGE_TAG \ --push .mode=maxguarda el cache de todas las etapas del multi-stage build (no solo la final), yimage-manifest=true,oci-mediatypes=truees necesario para que ECR acepte el manifiesto de cache. Es la opción más robusta: el cache deja de depender del host y pasa a ser un artefacto compartido del equipo. Requiere el builder que creamos enpre_build— y en el primer build,--cache-fromavisa que el tag:cachetodavía no existe y sigue sin él; a partir del segundo, lo encuentra.
Combinadas con una imagen base propia en ECR —misma región, sin rate limiting, sin salir de la red de AWS— estas capas convierten un build de 20 minutos en uno de segundos cuando el código no cambió, y de pocos minutos cuando sí.
Práctica guiada: crear el repositorio ECR
Abrir Amazon ECR
- Abrir Elastic Container Registry.
- En el panel lateral, asegurarse de estar en Private registry → Repositories.
Crear el repositorio de imágenes
- Pulsar Create repository.
- En Repository name, escribir
taller-aws-<su-nombre>(el mismo nombre usado en CodeCommit, por consistencia). - Dejar Image tag mutability en Mutable — esto permite reutilizar etiquetas
como
latestentre builds sucesivos. Es una simplificación para el laboratorio; las etiquetas de release que identifican un artefacto desplegable deben ser únicas. - Dejar las demás opciones con sus valores predeterminados y pulsar Create repository.
El repositorio aparece en la lista. Pulsar sobre su nombre y copiar el URI completo
—algo como 123456789012.dkr.ecr.us-east-1.amazonaws.com/taller-aws-maria. Se
necesitará al configurar CodeBuild.
Práctica guiada: crear y ejecutar el proyecto de CodeBuild
Abrir CodeBuild
- Abrir CodeBuild.
- Pulsar Create build project.
Configurar la fuente
- En Project name, escribir
taller-aws-<su-nombre>-build. - En la sección Source, seleccionar Source provider: AWS CodeCommit.
- En Repository, seleccionar el repositorio
taller-aws-<su-nombre>. - En Reference type, seleccionar Branch y elegir
main.
Aquí el build se inicia manualmente y toma main para practicar. En un flujo real, un
evento de CodeCommit o CodePipeline lo iniciaría al abrir un PR, fusionar un cambio o
crear un tag, según las reglas de promoción acordadas por el equipo.
Configurar el entorno de construcción
- En la sección Environment, seleccionar:
- Environment image: Managed image
- Compute: EC2
- Running mode: Container (en el Ejercicio 4 se explica esta opción)
- Operating system: Amazon Linux
- Runtime(s): Standard
- Image: seleccionar la versión más reciente disponible (por ejemplo
aws/codebuild/amazonlinux-x86_64-standard:6.0). - Image version: Always use the latest image for this runtime version.
- En Service role, seleccionar New service role. La consola propone el
nombre en Role name (
codebuild-taller-aws-<su-nombre>-build-service-role). Anotarlo —será necesario agregarle permisos de ECR a continuación. - Expandir Additional configuration y:
- En Privileged, activar la casilla Enable this flag if you want to build Docker images or want your builds to get elevated privileges. Es obligatoria para que CodeBuild pueda ejecutar el daemon de Docker y construir imágenes de contenedor.
- En Host kernel, seleccionar
kernel-6 (Amazon Linux 2023). Este campo elige el sistema operativo del host donde corre el contenedor del build (la imagen curada ya es Amazon Linux 2023). El valor predeterminado, Amazon Linux 2, sigue soportado, pero la consola muestra un aviso en el proyecto recomendando migrar. - En Compute, seleccionar 4 vCPUs, 8 GiB memory —la compilación de Rust dentro del build aprovecha los núcleos adicionales.
- Dejar Docker server configuration sin activar (en el Ejercicio 4 se explica esta opción).
Nota: si el proyecto ya fue creado con el valor predeterminado, la consola muestra un aviso azul recomendando el cambio. Se corrige en Edit project → Environment → Additional configuration → Host kernel → Amazon Linux 2023.
Agregar permisos de ECR al rol de CodeBuild
El rol creado automáticamente puede acceder a CodeCommit, pero aún no tiene permiso para publicar en ECR. Seguir estos pasos antes de ejecutar el build:
- En una nueva pestaña del navegador, abrir IAM → Roles y buscar el rol recién
creado (su nombre comienza con
codebuild-taller-aws-<su-nombre>). - Pulsar Add permissions → Attach policies.
- Buscar
AmazonEC2ContainerRegistryPowerUsery seleccionarlo. - Pulsar Add permissions. Volver a la pestaña de CodeBuild.
Configurar las variables de entorno
-
En la sección Environment, desplazarse hasta Additional configuration → Environment variables y agregar las siguientes variables:
Name Value Type AWS_ACCOUNT_IDEl ID de la cuenta AWS (12 dígitos, sin guiones) Plaintext IMAGE_REPO_NAMEtaller-aws-<su-nombre>Plaintext IMAGE_TAGlatestPlaintext Tip: el ID de cuenta se encuentra en la esquina superior derecha de la consola, bajo el nombre del usuario o rol.
Finalizar la configuración
- En la sección Buildspec, bajo Build specifications, seleccionar Use a buildspec file —la opción preseleccionada, Insert build commands, guarda los comandos en la configuración del proyecto en lugar de leerlos del repositorio.
- Dejar Buildspec name vacío: CodeBuild buscará automáticamente el archivo
buildspec.ymlen la raíz del repositorio. - En la sección Artifacts, seleccionar No artifacts —el resultado del build es la imagen publicada en ECR, no un artefacto de archivo. Esa imagen es el artefacto desplegable que usarán las etapas posteriores.
- Pulsar Create build project.
Ejecutar el build y seguir los logs
- En la vista del proyecto recién creado, pulsar Start build.
- CodeBuild aprovisiona el entorno y comienza a ejecutar los comandos del
buildspec.yml. La pestaña Build logs muestra la salida en tiempo real. - Seguir los logs. Se verán las cuatro fases: verificación de herramientas; lint,
autenticación con ECR y creación del builder;
docker buildx build(que construye y publica en un solo paso); y la verificación en ECR. Al final depre_build, el log imprime los tags resueltos para esta build, los URIs completos conlatest,branch-…y el SHA del commit. Estos deben coincidir con lo que luego aparece en ECR. La aplicación se compila dentro del build, por lo que la primera vez el proceso tarda entre 10 y 20 minutos. - Al terminar, el estado cambia a Succeeded (en verde) o Failed (en rojo). Si falla, el log indica en qué línea ocurrió el error.
Repasar los logs completos en CloudWatch
La pestaña Build logs muestra solo el tramo final de la salida. El log completo de cada build queda guardado en CloudWatch Logs:
-
Encima del log, pulsar el enlace View entire log. Se abre el log stream de ese build en CloudWatch Logs.
-
El mismo destino se alcanza desde CloudWatch → Log groups: CodeBuild crea un grupo por proyecto (
/aws/codebuild/taller-aws-<su-nombre>-build) y, dentro, un stream por build, identificado por el ID del build. En el stream, el campo Filter events busca texto en todo el log. Por ejemplo,"Tags resueltos"localiza el bloque impreso al final depre_build. -
Con la CLI:
export TALLER=taller-aws-<su-nombre> aws logs tail "/aws/codebuild/$TALLER-build" --since 1hCon
--follow, el comando queda esperando y va mostrando las líneas nuevas, útil para seguir un build en curso desde la terminal.
Verificar la imagen en ECR
- Volver a la consola de ECR y abrir el repositorio
taller-aws-<su-nombre>. - En la pestaña Images, se verá una fila por etiqueta recién publicada:
latest,branch-main, el SHA corto y el SHA completo del commit —los mismos tags que el log imprimió al final depre_build, máscache. El cache de capas que buildx exportó al registro y que acelerará los builds siguientes. Observar el digest: las cuatro primeras comparten el mismo, porque son la misma imagen con distintos nombres; esa es su identidad inmutable aunquelatestse actualice.
Copiar el Image URI de latest (no el de cache). Se necesitará en la
siguiente sección para lanzar el stack de CloudFormation.
-
La misma verificación con la CLI —
describe-imagesagrupa por digest, por lo que muestra dos filas: la imagen con todos sus tags, y el cache:aws ecr describe-images \ --repository-name "$TALLER" \ --query "sort_by(imageDetails,&imagePushedAt)[].{tags:join(', ',imageTags),pushed:imagePushedAt}" \ --output tablePor ejemplo:
❯ aws ecr describe-images \ --repository-name "$TALLER" \ --query "sort_by(imageDetails,&imagePushedAt)[].{tags:join(', ',imageTags),pushed:imagePushedAt}" \ --output table --------------------------------------------------------------------------------------------------------------------- | DescribeImages | +-----------------------------------+-------------------------------------------------------------------------------+ | pushed | tags | +-----------------------------------+-------------------------------------------------------------------------------+ | 2026-07-31T13:56:49.501000-03:00 | branch-main, cd5906be4801, latest, cd5906be4801c59a22b7c6816ea2683e85700fd1 | | 2026-07-31T13:57:23.661000-03:00 | cache | +-----------------------------------+-------------------------------------------------------------------------------+
ECR más allá del push: retención, replicación, acceso y escaneo
Con la primera imagen publicada, el repositorio ya cumple su rol mínimo: recibir imágenes y servirlas. Pero un registro de producción no se administra solo, y ECR trae cuatro funcionalidades que conviene conocer desde el primer día.
Políticas de lifecycle: decidir qué se guarda y por cuánto tiempo
Cada build de hoy dejó cuatro tags nuevos en el repositorio; un pipeline activo genera decenas por semana. El almacenamiento de ECR es barato (alrededor de $0.10 por GB al mes, un costo casi despreciable frente al resto de la infraestructura,) pero la buena práctica no pasa por el costo: pasa por definir de forma explícita cuál es la política de la empresa para la retención de imágenes, y expresarla como reglas en el repositorio en lugar de depender de limpiezas manuales.
Eso se hace con una Lifecycle policy (en la consola: dentro del repositorio,
Lifecycle policy). Una política es una lista de reglas; cada regla selecciona
imágenes por estado de tag (con tag, sin tag) y por prefijo o patrón (branch-*), y
las expira por antigüedad (sinceImagePushed) o por cantidad (imageCountMoreThan:
"conservar solo las últimas N"). Un detalle importante: una regla selecciona la
imagen completa. El digest con todos sus tags, no un tag individual.
Hay una trampa conocida: configurar la limpieza en términos de tiempo y borrar una imagen que está corriendo en producción. Mientras las tareas ya lanzadas sigan corriendo no pasa nada. El runtime ya tiene la imagen descargada. Pero en el momento en que haga falta escalar, reemplazar una tarea caída, o volver a registrar el servicio, el pull falla porque la imagen ya no existe. El fallo aparece justo cuando el sistema está bajo presión, que es el peor momento posible. ECR no sabe qué está desplegado: la política borra lo que las reglas seleccionan, esté en producción o no.
Cómo mitigar el borrado de imágenes en producción
- Preferir cantidad sobre tiempo para las imágenes desplegables. Una regla de "conservar las últimas N" garantiza historial aunque el proyecto pase meses sin desplegar; una regla de "borrar lo más viejo que X días", tras una temporada sin pushes, se lleva todas las imágenes, incluida la que está en producción.
- Separar los tags efímeros de los de release. Aplicar las reglas agresivas solo
a las imágenes sin tag y a los prefijos efímeros (
branch-*, SHAs); los tags de release (v*,release-*) reciben una retención larga, o ninguna regla. - Promocionar con un re-tag. Lo que llega a producción recibe un tag del espacio
protegido (
release-*), de modo que ninguna regla agresiva pueda seleccionarlo. - Ensayar antes de aplicar. La consola ofrece un preview de la política que lista qué imágenes borraría cada regla, sin borrar nada. Ninguna política debería activarse sin pasar por ahí.
Replicación entre regiones
En Private registry → Replication se configuran reglas de replicación: cada push a la región de origen se copia automáticamente a otras regiones, o a otras cuentas. Esto puede ser útil, y hasta necesario, al desarrollar un plan de Disaster Recovery: si la región primaria queda fuera de servicio, los artefactos de deploy ya existen en la región de recuperación, y el ambiente puede recrearse sin depender de la región caída. También reduce la latencia de pull en despliegues multi-región.
Dos detalles a tener en cuenta: la replicación copia los pushes, no los borrados. Una lifecycle policy de la región de origen no limpia las réplicas, así que cada región de destino necesita su propia política; y solo se replica lo que se publica después de crear la regla, lo ya existente no se copia retroactivamente.
Compartir imágenes fuera de la cuenta
En una organización real, las imágenes rara vez viven en la misma cuenta que las consume: un patrón común es una cuenta de shared services que construye y publica, y cuentas de desarrollo, staging y producción que solo hacen pull. ECR cubre ambos extremos del espectro: acceso privado entre cuentas conocidas, y publicación abierta al mundo.
De forma privada: repository policy
El acceso entre cuentas no requiere copiar nada. Cada repositorio acepta una repository policy —una política basada en recursos, editable en Permissions → Edit policy JSON dentro del repositorio, que declara quién puede hacer pull. Para que sea segura:
-
Conceder solo las acciones de pull:
ecr:BatchGetImage,ecr:GetDownloadUrlForLayeryecr:BatchCheckLayerAvailability. Nuncaecr:*. -
Nombrar a los consumidores de forma explícita: el ARN de cada cuenta (
arn:aws:iam::<cuenta>:root) o, mejor dentro de una organización, la condiciónaws:PrincipalOrgID— cualquier cuenta de la organización, y nadie más:{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPullFromOrg", "Effect": "Allow", "Principal": "*", "Action": [ "ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer", "ecr:BatchCheckLayerAvailability" ], "Condition": { "StringEquals": { "aws:PrincipalOrgID": "o-xxxxxxxxxx" } } } ] } -
Recordar que el permiso tiene dos puertas. En un acceso entre cuentas deben abrirse ambas: la repository policy del lado del repositorio, y la política de IAM del rol consumidor. Por ejemplo, el task execution role de un servicio ECS en la otra cuenta, que además necesita
ecr:GetAuthorizationTokenpara autenticarse.
Cuando las cuentas consumidoras son muchas, o están en otras regiones, la alternativa es la replicación cross-account de la sección anterior: cada cuenta hace pull de su propia copia local, y la policy se reduce a los permisos de replicación.
De forma pública: Amazon ECR Public
Cuando la imagen es para el mundo (ejemplo: una herramienta open source o una imagen base
propia) el mecanismo no es abrir el repositorio privado con un
"Principal": "*" sin condición. Para eso existe Amazon ECR Public: un registro
separado, con URLs de la forma public.ecr.aws/<alias>/... y una galería navegable
(gallery.ecr.aws.) La misma de donde el pull-through
cache del Ejercicio 6 sirve las imágenes oficiales de Docker. Se publica
autenticándose contra us-east-1; cualquiera puede hacer pull, incluso sin cuenta
de AWS (de forma anónima, con límites de tasa más bajos que autenticado).
Publicar una imagen expone todas sus capas: cualquier archivo copiado durante el
build, y los valores pasados como ARG, quedan descargables por cualquiera (basta
docker history para listarlos.) Antes de publicar: revisar que ningún secreto,
credencial o código privado haya entrado en una capa, y asumir que lo publicado ya
fue copiado — despublicar no lo recupera.
Escaneo de vulnerabilidades
En Private registry → Scanning se activa el escaneo de imágenes contra bases de CVEs conocidos. El nivel básico escanea el sistema operativo de la imagen en cada push; el enhanced scanning delega en Amazon Inspector y pasa a ser continuo: cubre también las dependencias de la aplicación, y re-escanea las imágenes existentes cada vez que se publica una vulnerabilidad nueva, trayendo los hallazgos al frente sin que nadie lance nada.
Los findings quedan visibles por imagen en la consola y se publican como eventos, lo que abre la puerta a automatizarlos. Al día de hoy es sencillo combinarlos con agentes que sugieren remediaciones fáciles (actualizar la imagen base, subir una dependencia) y las vuelcan directamente en el código mediante PRs automatizadas. La revisión sigue siendo humana, pero el ciclo completo (detectar, proponer, reconstruir, republicar,) corre sobre la misma estructura de CI/CD que se armó en esta sesión: ese es el valor de haberla construido.
Un adelanto: enterarse cuando el build termina
Hoy se lanzó el build a mano y se siguieron los logs en pantalla. En un equipo real nadie se queda mirando la consola: el build avisa solo cuando termina (en éxito o en error) por el canal donde el equipo ya conversa. Por ejemplo: Microsoft Teams.
No se configura esta semana, pero conviene ver el flujo desde ahora, porque es la pieza que cierra el pipeline en la Semana 3.
Cómo se notifica un build a Microsoft Teams
El evento de fin de build (o de un stage de CodePipeline) lo capturan las reglas de notificación de los Developer Tools, llamadas históricamente CodeStar Notifications, y lo publican en un tema de Amazon SNS. Desde SNS, AWS Chatbot lo entrega a un canal de Microsoft Teams.
CodeBuild / CodePipeline (evento)
→ CodeStar Notifications (regla)
→ Amazon SNS (tema)
→ AWS Chatbot
→ canal de Microsoft Teams
Una aclaración de nombres: el servicio AWS CodeStar (el de proyectos y dashboards) fue discontinuado en 2024. Las reglas de notificación que se usan aquí son otra cosa, siguen vigentes, y son parte de los Developer Tools.
En el laboratorio no se conecta Teams por participante, sería inviable. En su lugar, los eventos de la cuenta llegan a la aplicación del instructor, que los muestra como avisos (toasts) en esta misma guía. El mecanismo del lab es un espejo del flujo real: lo que aquí aparece como un toast, en la organización aparecería en un canal de Teams.
Ejercicio 3 — Crear el repositorio de imágenes
Crear un repositorio privado en Amazon ECR con el nombre taller-aws-<su-nombre>.
-
Abrir Elastic Container Registry.
-
En el panel lateral, seleccionar Private registry → Repositories.
-
Pulsar Create repository.
-
En Repository name, escribir
taller-aws-<su-nombre>. -
Dejar Image tag mutability en Mutable.
-
Pulsar Create repository.
-
En la lista de repositorios, pulsar sobre el nombre del repositorio recién creado.
-
Hacer click en la tab
Summary. -
Copiar el URI completo que aparece en la parte superior. Se necesitará para configurar CodeBuild y para el parámetro de CloudFormation en la sección siguiente. Con la CLI:
export TALLER=taller-aws-<su-nombre> aws ecr describe-repositories \ --repository-names "$TALLER" \ --query 'repositories[0].repositoryUri' \ --output textPor ejemplo:
❯ export TALLER=taller-aws-guzman aws ecr describe-repositories \ --repository-names "$TALLER" \ --query 'repositories[0].repositoryUri' \ --output text 410228653321.dkr.ecr.us-east-2.amazonaws.com/taller-aws-guzman
Ejercicio 4 — Ejecutar la primera build
Configurar un proyecto de CodeBuild que lea el repositorio de CodeCommit, construya la
imagen Docker usando el buildspec.yml incluido en el código, y la publique en el
repositorio de ECR. Ejecutar el build y verificar que la imagen aparece en ECR con la
etiqueta latest.
- En la consola de AWS, abrir CodeBuild y pulsar Create project.
- En Project name, escribir
taller-aws-<su-nombre>-build. - En Source provider, seleccionar AWS CodeCommit y luego el repositorio.
- En Reference type, elegir Branch → main.
Reference type define qué commit del repositorio se convierte en el artefacto:
- Branch: el último commit (HEAD) de la rama elegida. Es una referencia mutable —dos builds de la misma rama pueden construir código distinto.
- Git tag: el commit al que apunta un tag (por ejemplo
v1.4.0). Referencia inmutable, típica para releases. - Commit ID: un commit exacto, por SHA. La referencia más precisa de las tres.
El campo Commit ID – optional que aparece junto a Branch permite fijar el
build a un commit específico de esa rama; además, al saber exactamente qué descargar,
CodeBuild puede clonar menos historial y acortar el build. En el taller se deja vacío:
se construye siempre el HEAD de main.
- En Environment → Environment image, seleccionar Managed image.
- En Running mode, dejar Container seleccionado.
- Seleccionar Operating system: Amazon Linux, Runtime: Standard, la imagen
más reciente (por ejemplo
aws/codebuild/amazonlinux-x86_64-standard:6.0) y, en Image version, Always use the latest image for this runtime version. - En Service role, seleccionar New service role.
- Expandir Additional configuration y activar la casilla Privileged. Sin
esta opción, Docker no puede ejecutarse dentro del build y el proceso falla.
En Host kernel, seleccionar
kernel-6 (Amazon Linux 2023).
Running mode elige dónde corre el buildspec:
- Container: el build corre dentro de una imagen curada de
CodeBuild (
aws/codebuild/amazonlinux-x86_64-standard:6.0). Para construir imágenes, el daemon de Docker tiene que correr dentro de ese contenedor (Docker-in-Docker), y eso es exactamente lo que habilita la casilla Privileged. Es la vía clásica y la más documentada. - Instance: el buildspec corre directamente sobre la instancia EC2, sin contenedor intermedio. Docker es el daemon nativo del host: no hay Docker-in-Docker ni casilla de Privileged, con algo menos de overhead y una bandera de seguridad menos. A cambio, el entorno se elige por versión de máquina en lugar de imagen curada, y es un modo más nuevo y menos documentado.
- Docker server (al final de Additional configuration: Docker server
configuration → Enable docker server for this project): aprovisiona un daemon de
Docker remoto y persistente, dedicado al proyecto, con un cache de capas que
sobrevive entre builds; los comandos
dockerdel build se descargan automáticamente a ese servidor (documentación). Se cobra aparte del build: por segundo de servidor activo según el compute elegido, más una tarifa menor de cache at rest mientras está detenido (pricing). En el taller no hace falta: el cache en el registro (la etiquetacache) ya cumple ese rol.
-
En Additional configuration → Compute, seleccionar 4 vCPUs, 8 GiB memory.
-
En Additional configuration → Environment variables, agregar:
AWS_ACCOUNT_ID= el ID de la cuenta (12 dígitos)IMAGE_REPO_NAME=taller-aws-<su-nombre>IMAGE_TAG=latest
-
En Buildspec, bajo Build specifications, seleccionar Use a buildspec file (la consola preselecciona Insert build commands) y dejar Buildspec name vacío para que use el
buildspec.ymlde la raíz del repositorio. -
En Artifacts, seleccionar No artifacts.
-
Pulsar Create build project.
-
En IAM, buscar el rol cuyo nombre comienza con
codebuild-taller-aws-<su-nombre>, adjuntarle la políticaAmazonEC2ContainerRegistryPowerUser. Con la CLI:export TALLER=taller-aws-<su-nombre> ROLE=$(aws iam list-roles \ --query "Roles[?starts_with(RoleName, 'codebuild-$TALLER')].RoleName" \ --output text) aws iam attach-role-policy \ --role-name "$ROLE" \ --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser -
Volver a CodeBuild, abrir el proyecto, y pulsar Start build.
-
En la pestaña Build logs, seguir la ejecución hasta que el estado sea Succeeded. El log completo queda en CloudWatch Logs (View entire log, o el grupo
/aws/codebuild/taller-aws-<su-nombre>-build); con la CLI:aws logs tail "/aws/codebuild/$TALLER-build" --since 1h. -
En ECR, abrir el repositorio y confirmar las entradas con la fecha de hace unos minutos: la imagen con sus cuatro tags (
latest,branch-main, SHA corto y SHA completo, todos con el mismo digest) y el cache de capas de buildx con la etiquetacache. Con la CLI:aws ecr describe-images \ --repository-name "$TALLER" \ --query "sort_by(imageDetails,&imagePushedAt)[].{tags:join(', ',imageTags),pushed:imagePushedAt}" \ --output tablePor ejemplo:
❯ aws ecr describe-images \ --repository-name "$TALLER" \ --query "sort_by(imageDetails,&imagePushedAt)[].{tags:join(', ',imageTags),pushed:imagePushedAt}" \ --output table --------------------------------------------------------------------------------------------------------------------- | DescribeImages | +-----------------------------------+-------------------------------------------------------------------------------+ | pushed | tags | +-----------------------------------+-------------------------------------------------------------------------------+ | 2026-07-31T13:56:49.501000-03:00 | branch-main, cd5906be4801, latest, cd5906be4801c59a22b7c6816ea2683e85700fd1 | | 2026-07-31T13:57:23.661000-03:00 | cache | +-----------------------------------+-------------------------------------------------------------------------------+
Ejercicio 5 — Activar el cache local del proyecto
Editar el proyecto de CodeBuild para activar el cache local con los modos
SourceCache y DockerLayerCache. Ejecutar dos builds seguidos y comparar sus
duraciones en el historial del proyecto.
- Abrir CodeBuild,
entrar al proyecto
taller-aws-<su-nombre>-buildy pulsar Edit. - Desplazarse hasta la sección Artifacts y expandir Additional configuration.
- En Cache, seleccionar Local, y marcar las casillas Source cache y Docker layer cache (esta última requiere Privileged, que ya quedó activado al crear el proyecto).
- Pulsar Update project.
- Pulsar Start build y esperar a que termine.
- Sin dejar pasar tiempo, pulsar Start build otra vez.
- En la pestaña Build history, comparar la columna Duration de ambos builds: el segundo reutiliza la fuente descargada y las capas de Docker del primero.
El cache local es best effort: solo ayuda si el segundo build cae en el mismo host
que el primero, algo probable en builds consecutivos. Parte de la mejora del segundo
build viene además del cache de registro que el buildspec.yml publica en ECR bajo
el tag :cache — ese funciona desde cualquier host, y puede verse como una imagen
más en el repositorio.
Ejercicio 6 — Servir las imágenes base desde nuestro registro
Configurar un pull-through cache de la galería pública de ECR, y apuntar las dos
líneas FROM del Dockerfile a nuestro registro privado. Al terminar, ningún build
vuelve a descargar rust ni debian desde Docker Hub.
-
Abrir Elastic Container Registry y, en el panel lateral, seleccionar Private registry → Pull through cache.
-
Pulsar Add rule. En Registry, seleccionar Amazon ECR Public —no requiere credenciales— y pulsar Next.
-
Dejar el prefijo predeterminado
ecr-public, pulsar Next, y luego Create. La regla vale para todo el registro privado de la cuenta, una por región: cualquier repositorio bajo el prefijoecr-public/pasa a servirse a través del cache. -
Darle permiso al rol de CodeBuild para importar imágenes a través de la regla —la política
AmazonEC2ContainerRegistryPowerUserno lo incluye. En IAM → Roles, abrir el rolcodebuild-taller-aws-<su-nombre>-..., pulsar Add permissions → Create inline policy, cambiar a la pestaña JSON, y pegar (reemplazando el ID de cuenta):{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["ecr:BatchImportUpstreamImage", "ecr:CreateRepository"], "Resource": "arn:aws:ecr:*:<AWS_ACCOUNT_ID>:repository/ecr-public/*" } ] }Nombrarla
pull-through-cachey guardarla.ecr:CreateRepositorysolo hace falta la primera vez, mientras el repositorio de cache todavía no existe. -
En el clon local del repositorio, editar las dos líneas
FROMdelDockerfilepara que apunten al registro privado. La galería pública de ECR publica las imágenes oficiales de Docker bajodocker/library/, con el mismo digest que Docker Hub, así que los pins@sha256:no cambian —solo cambia el host:FROM <AWS_ACCOUNT_ID>.dkr.ecr.<region>.amazonaws.com/ecr-public/docker/library/rust:1.95-slim-bookworm@sha256:d7482085ff5b415f84dba5647ae71606650bdef00db7aeb69f4b3d170c3e4082 AS builderFROM <AWS_ACCOUNT_ID>.dkr.ecr.<region>.amazonaws.com/ecr-public/docker/library/debian:bookworm-slim@sha256:7b140f374b289a7c2befc338f42ebe6441b7ea838a042bbd5acbfca6ec875818 -
Confirmar y publicar el cambio:
git add Dockerfile,git commit,git push. -
En CodeBuild, pulsar Start build. En los logs, el pull de las imágenes base ahora sale de
dkr.ecr— la primera vez, ECR las importa del upstream; las siguientes, las sirve directo desde el cache. -
Al terminar, volver a ECR: aparecen dos repositorios nuevos,
ecr-public/docker/library/rustyecr-public/docker/library/debian, con las imágenes base cacheadas.
Con la regla activa, ECR revalida cada etiqueta contra el upstream a lo sumo una vez cada 24 horas — y si el upstream no responde, sirve la última versión cacheada: el rate limiting de Docker Hub y sus caídas dejan de ser un problema del pipeline. El pin por digest sigue haciendo su trabajo: aunque la etiqueta avance en el upstream, el build usa exactamente la imagen fijada.