En este tema aprenderemos cómo integrar Docker dentro de un flujo de trabajo de Integración Continua y Entrega/Despliegue Continuo (CI/CD). Docker se ha convertido en una pieza fundamental de los pipelines modernos porque permite construir artefactos reproducibles (imágenes) que se comportan igual en cualquier entorno. Comprender esta integración es esencial para automatizar la construcción, las pruebas, la publicación y el despliegue de tus aplicaciones de forma fiable.
Objetivos de Aprendizaje
- Entender qué es CI/CD y por qué Docker encaja de forma natural en estos procesos.
- Conocer las fases típicas de un pipeline: construir (build), probar (test) y publicar (push) imágenes.
- Crear un pipeline de ejemplo con GitHub Actions y con GitLab CI usando un
Dockerfile. - Aplicar buenas prácticas de versionado y etiquetado (tagging) de imágenes en CI.
- Automatizar el despliegue y gestionar las credenciales del registro de forma segura mediante secrets.
¿Qué es CI/CD y Por Qué Docker Encaja?
CI/CD es un conjunto de prácticas que automatizan el ciclo de vida del software. Conviene distinguir tres conceptos:
- CI (Integración Continua): cada cambio que se sube al repositorio se construye y se prueba automáticamente, detectando errores cuanto antes.
- CD (Entrega Continua): además de construir y probar, se prepara automáticamente un artefacto listo para desplegar, pero el despliegue final lo aprueba una persona.
- CD (Despliegue Continuo): el artefacto validado se despliega de forma totalmente automática, sin intervención humana.
Docker encaja perfectamente en este modelo por varias razones:
| Característica de Docker | Beneficio en CI/CD |
|---|---|
| Imágenes inmutables | El mismo artefacto se prueba y se despliega, sin sorpresas. |
| Reproducibilidad | Se construye igual en el portátil del desarrollador y en el servidor de CI. |
| Aislamiento | Cada job se ejecuta en un entorno limpio y controlado. |
| Portabilidad | La imagen se ejecuta igual en cualquier nodo o nube. |
| Versionado por tags | Cada build genera una versión identificable y trazable. |
La idea central es: "construyes una imagen una sola vez y promueves ese mismo artefacto a través de los entornos" (pruebas, staging, producción).
Fases de un Pipeline con Docker
Un pipeline típico basado en Docker sigue tres fases principales:
- Build (Construir): se construye la imagen a partir del
Dockerfile. - Test (Probar): se ejecutan las pruebas dentro de un contenedor creado desde esa imagen.
- Push (Publicar): si las pruebas pasan, la imagen se etiqueta y se sube a un registro (registry) como Docker Hub, GitHub Container Registry o GitLab Registry.
Opcionalmente se añade una cuarta fase:
- Deploy (Desplegar): se actualiza el entorno de destino para que use la nueva imagen.
Ejemplo de Dockerfile de Partida
Trabajaremos con una aplicación de ejemplo muy sencilla. Este es el Dockerfile:
# Imagen base ligera con Node.js
FROM node:20-alpine
# Directorio de trabajo dentro del contenedor
WORKDIR /app
# Copiamos primero los manifiestos de dependencias para aprovechar la caché
COPY package*.json ./
# Instalamos solo las dependencias de producción
RUN npm ci --omit=dev
# Copiamos el resto del código fuente
COPY . .
# Puerto que expone la aplicación
EXPOSE 3000
# Comando que arranca la aplicación
CMD ["node", "server.js"]Explicación de cada fragmento:
- FROM node:20-alpine: parte de una imagen oficial de Node.js basada en Alpine Linux, que es muy ligera.
- WORKDIR /app: define
/appcomo directorio de trabajo; los comandos siguientes se ejecutan ahí. - COPY package*.json ./: copia solo los ficheros de dependencias antes que el código. Así Docker reutiliza la capa de
npm cisi las dependencias no cambian, acelerando builds posteriores. - RUN npm ci --omit=dev: instala las dependencias de forma reproducible a partir del
package-lock.json, excluyendo las de desarrollo. - COPY . .: copia el resto del proyecto al contenedor.
- EXPOSE 3000: documenta que la aplicación escucha en el puerto 3000.
- CMD ["node", "server.js"]: define el comando por defecto al arrancar el contenedor.
Pipeline con GitHub Actions
GitHub Actions permite definir pipelines en ficheros YAML dentro de .github/workflows/. Veamos un workflow completo que construye, prueba, etiqueta y publica la imagen.
name: Build, Test y Push de la imagen Docker
# Se ejecuta en cada push a main y en cada etiqueta de versión
on:
push:
branches:
- main
tags:
- "v*.*.*"
jobs:
docker:
runs-on: ubuntu-latest
steps:
# 1. Descargamos el código del repositorio
- name: Checkout del código
uses: actions/checkout@v4
# 2. Configuramos Buildx para builds avanzados
- name: Configurar Docker Buildx
uses: docker/setup-buildx-action@v3
# 3. Iniciamos sesión en el registro usando secrets
- name: Login en el registro
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_TOKEN }}
# 4. Construimos la imagen (sin publicar) y la cargamos localmente
- name: Construir imagen
run: docker build -t miapp:ci .
# 5. Ejecutamos las pruebas dentro de un contenedor
- name: Ejecutar pruebas
run: docker run --rm miapp:ci npm test
# 6. Construimos y publicamos con tags adecuados
- name: Build y Push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/miorg/miapp:latest
ghcr.io/miorg/miapp:${{ github.sha }}Explicación detallada:
- on: define los eventos que disparan el pipeline. Aquí se activa con pushes a la rama
mainy con etiquetas que sigan el patrónv*.*.*(por ejemplov1.2.0). - runs-on: ubuntu-latest: el job se ejecuta en una máquina virtual Linux proporcionada por GitHub.
- actions/checkout@v4: clona el repositorio dentro del runner.
- docker/setup-buildx-action@v3: habilita Buildx, el motor de construcción extendido de Docker.
- docker/login-action@v3: autentica contra el registro. Las credenciales nunca van en texto plano:
secrets.GHCR_TOKENes un secreto configurado en el repositorio. - docker build -t miapp:ci .: construye una imagen temporal llamada
miapp:cipara las pruebas. - docker run --rm miapp:ci npm test: arranca un contenedor desde esa imagen y ejecuta los tests;
--rmelimina el contenedor al terminar. - docker/build-push-action@v6: vuelve a construir y, esta vez, publica la imagen con dos etiquetas:
latesty el hash del commit.
Pipeline con GitLab CI
GitLab CI se configura con un fichero .gitlab-ci.yml en la raíz del proyecto. Este ejemplo usa el ejecutor Docker-in-Docker (dind).
stages:
- build
- test
- push
variables:
IMAGE: $CI_REGISTRY_IMAGE/miapp
# Construye la imagen y la guarda con la etiqueta del commit
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker build -t $IMAGE:$CI_COMMIT_SHORT_SHA .
# Ejecuta las pruebas dentro de la imagen recién construida
test:
stage: test
image: docker:24
services:
- docker:24-dind
script:
- docker build -t $IMAGE:test .
- docker run --rm $IMAGE:test npm test
# Inicia sesión y publica la imagen en el registro de GitLab
push:
stage: push
image: docker:24
services:
- docker:24-dind
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
- docker build -t $IMAGE:$CI_COMMIT_SHORT_SHA -t $IMAGE:latest .
- docker push $IMAGE:$CI_COMMIT_SHORT_SHA
- docker push $IMAGE:latest
only:
- mainExplicación detallada:
- stages: define el orden de las fases (
build,test,push). - variables:
IMAGEse construye conCI_REGISTRY_IMAGE, una variable que GitLab rellena automáticamente con la ruta del registro del proyecto. - services: docker:24-dind: levanta el servicio Docker-in-Docker, necesario para ejecutar comandos
dockerdentro del job. - docker login --password-stdin: la contraseña se pasa por la entrada estándar en lugar de como argumento, evitando que aparezca en los logs.
CI_REGISTRY_USERyCI_REGISTRY_PASSWORDson variables protegidas que GitLab inyecta de forma segura. - only: main: el job
pushsolo se ejecuta para la ramamain.
Versionado y Etiquetado (Tagging) de Imágenes
Etiquetar bien las imágenes es clave para la trazabilidad. Estas son las estrategias más habituales:
| Etiqueta | Ejemplo | Uso recomendado |
|---|---|---|
latest |
miapp:latest |
Última versión estable; cómoda pero poco precisa. |
| SHA del commit | miapp:9f8c2a1 |
Trazabilidad exacta de qué código contiene la imagen. |
| Versión semántica | miapp:1.4.2 |
Releases formales (SemVer: MAYOR.MENOR.PARCHE). |
| Rama o entorno | miapp:staging |
Imagen asociada a un entorno concreto. |
Buenas prácticas:
- Evita depender solo de
latesten producción: no indica qué versión real estás ejecutando. - Etiqueta siempre con el SHA del commit para poder reproducir exactamente cualquier despliegue.
- Para releases, usa versionado semántico generado a partir de las etiquetas de Git.
Despliegue Automatizado
Una vez publicada la imagen, la fase de despliegue actualiza el entorno para que use la nueva versión. Un ejemplo sencillo, conectándose por SSH a un servidor, sería:
# Conexión al servidor de destino y actualización del contenedor
ssh deploy@servidor-produccion <<'EOF'
docker pull ghcr.io/miorg/miapp:1.4.2
docker stop miapp || true
docker rm miapp || true
docker run -d --name miapp -p 80:3000 ghcr.io/miorg/miapp:1.4.2
EOFExplicación:
- docker pull ...: descarga la nueva versión de la imagen en el servidor.
- docker stop / docker rm: detiene y elimina el contenedor antiguo;
|| trueevita que el script falle si no existía. - docker run -d ...: arranca el contenedor nuevo en segundo plano, mapeando el puerto 80 del host al 3000 del contenedor.
En entornos más avanzados, este paso se delega a orquestadores como Docker Swarm o Kubernetes, que veremos en los próximos temas.
Gestión Segura de Credenciales mediante Secrets
Nunca debes escribir contraseñas, tokens o claves directamente en los ficheros del pipeline, ya que quedarían visibles en el repositorio. En su lugar, usa el almacén de secretos de tu plataforma:
- GitHub Actions: secretos en Settings → Secrets and variables → Actions. Se referencian como
${{ secrets.NOMBRE }}. - GitLab CI: variables protegidas y enmascaradas en Settings → CI/CD → Variables.
Reglas de oro:
- Usa tokens de acceso con permisos mínimos (solo lectura/escritura del registro), no contraseñas de cuenta.
- Marca las variables como "enmascaradas" para que no aparezcan en los logs.
- Rota los tokens periódicamente y revócalos si dejan de usarse.
- Prefiere
--password-stdinfrente a pasar la contraseña como argumento de línea de comandos.
Errores Comunes y Consejos
- Usar
latesten todas partes: dificulta saber qué versión está desplegada. Etiqueta con SHA o SemVer. - Credenciales en texto plano: nunca pongas tokens en el YAML; usa siempre secrets.
- No cachear capas: copiar el código antes que los manifiestos de dependencias invalida la caché y ralentiza los builds. Copia primero
package*.json(o equivalente). - Probar una imagen distinta de la que se publica: asegúrate de probar y publicar el mismo artefacto para evitar discrepancias.
- Imágenes enormes: usa imágenes base ligeras (
alpine) y multi-stage builds para reducir tamaño. - Olvidar
--rmen los contenedores de prueba: acumula contenedores muertos en el runner.
Ejercicios
Ejercicio 1
Escribe un workflow de GitHub Actions que, en cada push a main, construya una imagen Docker de un proyecto y la etiquete con el SHA del commit. No es necesario publicarla todavía.
Ejercicio 2
Modifica el ejercicio anterior para que, antes de etiquetar, ejecute las pruebas dentro de un contenedor creado desde la imagen recién construida.
Ejercicio 3
Explica por qué es mala práctica desplegar siempre la etiqueta latest en producción y propón una alternativa.
Soluciones
Solución al Ejercicio 1:
name: Build de la imagen
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Construir imagen etiquetada con el SHA
run: docker build -t miapp:${{ github.sha }} .El workflow se dispara en cada push a main, clona el código y construye la imagen usando github.sha como etiqueta.
Solución al Ejercicio 2:
name: Build y Test
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Construir imagen
run: docker build -t miapp:${{ github.sha }} .
- name: Ejecutar pruebas
run: docker run --rm miapp:${{ github.sha }} npm testSe añade un paso que arranca un contenedor desde la imagen recién construida y ejecuta npm test. Si las pruebas fallan, el job falla y el flujo se detiene.
Solución al Ejercicio 3:
Desplegar siempre latest es mala práctica porque la etiqueta no identifica una versión concreta: el contenido al que apunta cambia con cada publicación. Esto impide saber qué código está realmente en producción, complica los rollbacks y puede provocar que dos servidores ejecuten versiones distintas con la misma etiqueta. La alternativa es etiquetar cada imagen con el SHA del commit o con una versión semántica (1.4.2) y desplegar esa etiqueta inmutable.
Conclusión
En este tema hemos visto cómo Docker se integra en los pipelines de CI/CD para construir, probar, etiquetar y publicar imágenes de forma automática y reproducible. Hemos creado ejemplos con GitHub Actions y GitLab CI, aprendido buenas prácticas de versionado y comprendido cómo proteger las credenciales del registro con secrets. Con una imagen publicada y versionada, el siguiente paso natural es ejecutarla a escala: en el próximo tema, Orquestando Contenedores con Docker Swarm, aprenderemos a desplegar y gestionar contenedores en un clúster.
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
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de 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
- Redes en Docker
- Persistencia de Datos con Volúmenes
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
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
- Registro y Monitoreo en Docker
Módulo 6: Docker en 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
