--- title: "Pipeline de Jenkins para construir imágenes Docker" description: "Crea un pipeline declarativo de Jenkins que construye una imagen Docker, ejecuta tests, la analiza con Trivy y la sube a Docker Hub con credenciales seguras." category: "CI/CD DevOps" language: es source: https://cubepath.com/es/docs/ci-cd-devops/pipeline-de-jenkins-para-compilaciones-docker updated: 2026-09-25 --- # Pipeline de Jenkins para construir imágenes Docker Jenkins puede construir, probar y publicar imágenes Docker en cada cambio del repositorio si describes el proceso en un `Jenkinsfile`. En este tutorial prepararás un servidor Jenkins en Ubuntu 24.04 para usar Docker, crearás una pequeña aplicación de ejemplo con un Dockerfile multietapa y escribirás un pipeline declarativo que ejecuta los tests dentro de la imagen, la analiza con Trivy y la publica en Docker Hub. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM. - Un usuario no root con privilegios `sudo`. - Jenkins instalado desde el repositorio oficial y accesible por navegador, con los plugins sugeridos del asistente inicial (incluyen Pipeline, Git y Credentials Binding). - Docker Engine instalado en el mismo servidor desde el repositorio oficial de Docker. - Una cuenta de Docker Hub y un repositorio Git (GitHub, GitLab o similar) donde subir el código de ejemplo. ## Paso 1: Permitir que Jenkins use Docker Jenkins se ejecuta con el usuario de sistema `jenkins`, que por defecto no puede hablar con el daemon de Docker. Añádelo al grupo `docker`: ```bash sudo usermod -aG docker jenkins ``` El cambio de grupo solo se aplica a procesos nuevos, así que reinicia Jenkins: ```bash sudo systemctl restart jenkins ``` Comprueba que el usuario `jenkins` puede ejecutar comandos de Docker: ```bash sudo -u jenkins docker version --format '{{.Server.Version}}' ``` Deberías ver la versión del servidor Docker, por ejemplo: ```output 28.3.3 ``` > Advertencia: pertenecer al grupo `docker` equivale en la práctica a tener acceso root en el servidor. Usa este esquema en un servidor Jenkins dedicado y limita quién puede crear o modificar trabajos. ## Paso 2: Crear la aplicación de ejemplo El pipeline necesita un repositorio con un Dockerfile y algo que probar. En tu equipo, crea un directorio para el proyecto: ```bash mkdir myapp && cd myapp ``` Crea una aplicación Flask mínima con un endpoint de salud: ```bash nano app.py ``` ```python from flask import Flask app = Flask(__name__) @app.get("/health") def health(): return {"status": "ok"} ``` Añade un test que la compruebe: ```bash nano test_app.py ``` ```python from app import app def test_health(): client = app.test_client() response = client.get("/health") assert response.status_code == 200 assert response.get_json() == {"status": "ok"} ``` Declara las dependencias de ejecución en `requirements.txt`: ```text flask gunicorn ``` Y las de desarrollo en `requirements-dev.txt`: ```text pytest ``` Ahora crea el `Dockerfile`. Usa tres etapas: `base` instala las dependencias, `test` ejecuta pytest y `runtime` es la imagen final, sin herramientas de test y con un usuario sin privilegios: ```bash nano Dockerfile ``` ```dockerfile FROM python:3.12-slim AS base WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app.py . FROM base AS test COPY requirements-dev.txt test_app.py ./ RUN pip install --no-cache-dir -r requirements-dev.txt && pytest -q FROM base AS runtime RUN useradd --create-home appuser USER appuser EXPOSE 8000 CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"] ``` BuildKit solo construye las etapas que necesita el objetivo pedido. Por eso el pipeline construirá primero `--target test` (si pytest falla, falla la construcción) y después la imagen final, que reutiliza la caché de la etapa `base`. Sube estos archivos a tu repositorio Git en la rama `main`. El `Jenkinsfile` lo añadirás en el paso 4. ## Paso 3: Guardar las credenciales de Docker Hub en Jenkins No escribas contraseñas en el `Jenkinsfile`. En su lugar, crea un token de acceso en Docker Hub (**Account settings > Personal access tokens > Generate new token**) con permisos de lectura y escritura, y guárdalo en Jenkins: 1. Ve a **Manage Jenkins > Credentials > System > Global credentials (unrestricted) > Add Credentials**. 2. En **Kind** elige **Username with password**. 3. En **Username** escribe tu usuario de Docker Hub y en **Password** pega el token. 4. En **ID** escribe `dockerhub` y guarda. El pipeline hará referencia a la credencial por ese ID. Jenkins enmascara su valor en los logs de la construcción. ## Paso 4: Escribir el Jenkinsfile Crea el archivo `Jenkinsfile` en la raíz del repositorio. Sustituye `your_dockerhub_user` por tu usuario de Docker Hub: ```bash nano Jenkinsfile ``` ```groovy pipeline { agent any environment { IMAGE = 'your_dockerhub_user/myapp' TAG = "${env.BUILD_NUMBER}" } options { buildDiscarder(logRotator(numToKeepStr: '20')) disableConcurrentBuilds() } stages { stage('Test') { steps { sh 'docker build --target test -t "$IMAGE:test-$TAG" .' } } stage('Build') { steps { sh 'docker build --target runtime -t "$IMAGE:$TAG" -t "$IMAGE:latest" .' } } stage('Scan') { steps { sh ''' docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy:latest image \ --exit-code 1 --severity CRITICAL --no-progress \ "$IMAGE:$TAG" ''' } } stage('Push') { steps { withCredentials([usernamePassword(credentialsId: 'dockerhub', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')]) { sh 'echo "$REG_PASS" | docker login -u "$REG_USER" --password-stdin' sh 'docker push "$IMAGE:$TAG"' sh 'docker push "$IMAGE:latest"' } } } } post { always { sh 'docker logout || true' sh 'docker image rm "$IMAGE:test-$TAG" "$IMAGE:$TAG" "$IMAGE:latest" || true' } } } ``` Qué hace cada parte: - **Test**: construye la etapa `test` del Dockerfile. Si algún test falla, `docker build` devuelve error y el pipeline se detiene. - **Build**: construye la imagen final con dos etiquetas, el número de construcción (inmutable, útil para desplegar y volver atrás) y `latest`. - **Scan**: ejecuta Trivy contra la imagen local y hace fallar el pipeline si encuentra vulnerabilidades de severidad `CRITICAL`. - **Push**: `withCredentials` expone el usuario y el token solo dentro del bloque. Los comandos van entre comillas simples para que sea el shell, y no Groovy, quien expanda `$REG_PASS`; así el secreto no aparece en el script generado. - **post**: cierra la sesión del registro y borra las imágenes locales para no llenar el disco del servidor, aunque el pipeline falle. Sube el `Jenkinsfile` al repositorio: ```bash git add Jenkinsfile git commit -m "Add Jenkins pipeline" git push origin main ``` ## Paso 5: Crear el trabajo de pipeline en Jenkins 1. En el panel de Jenkins, pulsa **New Item**, escribe `myapp`, elige **Pipeline** y pulsa **OK**. 2. En la sección **Pipeline**, cambia **Definition** a **Pipeline script from SCM**. 3. En **SCM** elige **Git** e introduce la URL del repositorio. Si es privado, añade una credencial de Git en **Credentials**. 4. En **Branch Specifier** escribe `*/main` y deja **Script Path** como `Jenkinsfile`. 5. Guarda y pulsa **Build Now**. La primera ejecución tarda más porque descarga las imágenes `python:3.12-slim` y `aquasec/trivy`. Abre la construcción y entra en **Console Output**. Al final deberías ver algo parecido a: ```output + docker push your_dockerhub_user/myapp:1 ... 1: digest: sha256:3f5c... size: 1994 ... Finished: SUCCESS ``` La vista **Stages** muestra las cuatro etapas en verde. ## Paso 6: Verificar la imagen publicada Desde cualquier máquina con Docker, descarga la imagen con la etiqueta del número de construcción y ejecútala: ```bash docker run -d --rm --name myapp-check -p 8000:8000 your_dockerhub_user/myapp:1 ``` Comprueba el endpoint de salud: ```bash curl http://localhost:8000/health ``` ```output {"status":"ok"} ``` Detén el contenedor de prueba: ```bash docker stop myapp-check ``` Para comprobar que el pipeline protege la rama, cambia el test para que falle, haz push y lanza otra construcción: debe detenerse en la etapa **Test** y no publicar nada. ## Paso 7: Construir automáticamente en cada push (opcional) Pulsar **Build Now** a mano no escala. Tienes dos opciones: - **Webhook**: en la configuración del trabajo, marca el disparador que ofrezca tu plugin de SCM (por ejemplo **GitHub hook trigger for GITScm polling** con el plugin de GitHub) y crea en el repositorio un webhook hacia `https://your_jenkins_domain/github-webhook/`. - **Sondeo**: marca **Poll SCM** con una programación como `H/5 * * * *`. Jenkins comprobará cambios cada cinco minutos, sin exponer Jenkins a Internet. Si más adelante usas un trabajo **Multibranch Pipeline**, puedes limitar la etapa **Push** a la rama principal añadiendo `when { branch 'main' }` dentro de esa etapa. ## Solución de problemas **`permission denied while trying to connect to the Docker daemon socket`**: el proceso de Jenkins no tiene el grupo `docker`. Comprueba con `id jenkins` que aparece el grupo y reinicia Jenkins con `sudo systemctl restart jenkins`. **`docker: not found`**: Docker no está instalado en el nodo donde se ejecuta el pipeline. Si usas agentes, instala Docker en ellos o restringe el trabajo con una etiqueta (`agent { label 'docker' }`). **`unauthorized: incorrect username or password` en el push**: el token de Docker Hub ha caducado o no tiene permiso de escritura, o el ID de la credencial no es `dockerhub`. Genera un token nuevo y actualiza la credencial. **La etapa Scan falla**: Trivy ha encontrado vulnerabilidades críticas. Revisa la tabla en el log; normalmente se resuelve actualizando la imagen base o la dependencia afectada. ## Conclusión Tienes un pipeline de Jenkins que construye una imagen Docker, ejecuta los tests dentro de ella, la analiza con Trivy y la publica en Docker Hub solo si todo lo anterior ha ido bien, sin exponer credenciales en el código. Como siguientes pasos, puedes publicar en un registro privado como Harbor cambiando `IMAGE` y la credencial, añadir análisis de calidad de código con SonarQube o desplegar la imagen etiquetada con el número de construcción en tu servidor de producción.