Tabla de contenidos
- Introducción
- Requisitos previos
- Configuraciones en Jenkins
- Creación de Pipeline en Jenkins
- Lo malo
- Lo bueno
- Conclusión
Introducción
Esta publicación será un poco extensa, ya que abarcaré temas cubiertos en entregas anteriores, donde detallé la instalación de Jenkins y SonarQube, ideal si estás comenzando desde cero y necesitas una base sólida.
Esta guía está totalmente basada para proyectos de java en Springboot, pero casi en su totalidad podrás utilizarla para cualquier otro lenguaje.
El objetivo central de esta guía es mostrar cómo automatizar el proceso de despliegue para ejecutar pruebas de manera eficiente. A menudo, cuando trabajas en un entorno local con tecnologías como Spring Boot o NPM, es común realizar pruebas directamente en tu IDE. Sin embargo, ¿qué sucede cuando deseas llevar esas pruebas a un entorno más robusto como Docker? La automatización de este proceso no solo facilita las pruebas, sino que también abre un abanico de posibilidades para gestionar el ciclo de vida de tu aplicación.
Además, contar con la integración de SonarQube te permitirá obtener una evaluación objetiva de la calidad de tu código, garantizando estándares más altos en cada iteración del desarrollo.
Requisitos previos
Es importante tener estos requisitos instalados, ya que esta guía está totalmente basada en estas publicaciones.
- Instala Docker en Ubuntu 24.04 LTS
- Instala Portainer en Docker Linux
- Instala Jenkins en Docker Linux
- Instala SonarQube en Docker Linux
Una vez que te asegures de que los contenedores de Docker de todos estos servicios están activos, puedes continuar con la guía.
Para ver los contenedores activos, puedes verlo desde Portainer o usando el siguiente comando:
sudo docker ps
Configuraciones en Jenkins
Enlazando Jenkins con GitHub
La principal característica de este sistema, es tener nuestro código alojado en servicios como GitLab, GitHub o BitBucket, ya sea en uno de los servidores locales o en servicios ya establecidos. Esta guía se basa en Github.
Obteniendo llaves privadas SSH
Para este paso, debes estar seguro que estás en la máquina donde está alojado el servicio Docker de Jenkins y que tienes Git instalado. Sino lo tienes, puedes instalarlo con el siguiente comando:
sudo apt update
sudo apt install git
git –version
Para generar la llave o claves SSH, voy a usar el siguiente comando, cambiando el correo por el mío:
ssh-keygen -t rsa -b 4096 -C tu_email@ejemplo.com
Le di enter a todo, pero si quisieras usar un passphrase o cambiar la ubicación de guardado, es opción tuya.

Ahora hay que agregar la clave que se creó al Agente SSH:
ssh-add ~/.ssh/id_rsa
Tiene que aparecer algo como lo siguiente:
Identity added: /home/docker/.ssh/id_rsa (tu_email@mail.com)
Dar de alta la SSH Key en GitHub
Es momento de ir a GitHub y dar de alta la clave que se generó, para visualizar la clave, podemos hacerlo con el siguiente comando, esto si lo hicimos guardando la clave de forma predeterminada:
cat ~/.ssh/id_rsa.pub

Esa gran cadena de texto hay que copiarla, desde el “ssh-rsa” hasta donde termina el correo electrónico y vamos a las preferencias de GitHub para ubicar las llaves SSH:https://github.com/settings/keys
Seleccionas “New SSH key“

Nombras la llave, en este caso podría ser “Jenkins”, el “Key type” se queda en “Authentication Key”, en el recuadro de abajo va la llave SSH que se generó.

Finalizas dándole al botón “Add SSH key” y ya tendrías GitHub enlazado al Git de la máquina host de Docker.
Para comprobar que se realizó el alta de la llave SSH, puedes lanzar el siguiente comando:
ssh -T git@github.com
Debe arrojar una salida parecida a esta:
Hi Tecsharp! You’ve successfully authenticated, but GitHub does not provide shell access.
Conectando Jenkins con GitHub
Accedemos a la siguiente pantalla en Jenkins, desde la ruta principal, de lado izquierdo hay un menú que se llama “Administrar Jenkins” o podemos acceder desde esta URL: http://localhost:8080/manage/credentials/
Selecciona “System” dentro de “Stores scoped to Jenkins”.

Agregas un dominio, le puedes poner “GitHub” porque es donde van a ir las credenciales de GitHub.


Dar clic a “Add credentials”

La configuración de las credenciales tiene que quedar parecido a lo siguiente:
Kind: SSH Username with private Key
Scope: Global
ID: se genera automáticamente después de crear la credencial
Description: GitHub Credentials
Username: tu usuario de GitHub
Private Key: se selecciona “Enter directly” y pegas la clave privada SSH que se generó. Puedes ver esa clave con el siguiente comando:
cat ~/.ssh/id_rsa
Esta clave inicia con: —–BEGIN OPENSSH PRIVATE KEY—– y termina con: —–END OPENSSH PRIVATE KEY—–
Para terminar, dale al botón de Create.

La credencial al final se vería algo así, hay que guardar el ID, siempre lo podrás consultar en las credenciales, pero este ID se va a utilizar al momento de crear el pipeline de Jenkins, por lo que hay que tenerlo a la mano.

Enlazando Jenkins con SonarQube
Instalando plugins en Jenkins
Para que SonarQube tenga la posibilidad de conetarse con Jenkins, hay que instalar el plugin de SonarQube desde Jenkins. Puedes dirigirte a “Administrar Jenkins > Plugins > Available plugins” e instalar los siguientes plugins:
- Blue Ocean
- Maven Integration plugin
- SonarQube Scanner for Jenkins
- Sonar Quality Gates Plugin
- Pipeline: Stage View

La instalación de los plugins es rápida y fácil, sólo buscas, seleccionas e instalas, aprovecha para seleccionar la opción de “Reiniciar Jenkins cuando termine la instalación y no queden trabajos en ejecución” mientras se instalan los plugins.
Al finalizar el proceso de instalación de plugins, seguramente te saque de la sesión, por lo que deberás iniciar sesión nuevamente.
Configurando SonarQube en Jenkins
Nuevamente hay que dar de alta una credencial, pero esta vez no crearemos un dominio, sino que usaremos el dominio global unrestricted:
“Administrar Jenkins > Credentials > System > Global credentials (unrestricted)” o desde el siguiente link:http://localhost:8080/manage/credentials/store/system/domain/_/

En este momento, hay que ir a buscar el token en la aplicación de SonarQube, así que para eso, vamos a SonarQube y nos dirigimos a “Administration > Security > Users” o ingresamos a la siguiente URL: http://localhost:9000/admin/users
Da clic en los tres puntitos, donde marca el recuadro amarillo.

Va a saltar la siguiente ventana, donde el nombre bien puede ser “sonar-jenkins” y la expiración en “No expiration”, después dar clic en “Generate”


Este movimiento arrojará un token, el token es el que inicia con “squ_”, que se va a utilizar en Jenkins, así que lo copiamos e inmediatamente lo llevamos al paso anterior, donde estaba la nueva credencial de Jenkins.
Cabe resaltar que este token se va a utilizar en el pipeline, claro, puedes crear otro si este se te llega a perder, pero mejor guardarlo para cuando se cree el pipeline.
La credencial de SonarQube en Jenkins quedaría así, en el campo “Secret” va el token que sacamos de SonarQube.

Ya una vez que creamos la credencial de SonarQube, hay que tener en cuenta que este ID también lo vamos a poder ver cuando queramos, además de que se utiliza en el pipeline de Jenkins, así que hay que tenerlo a la mano, al final la credencial quedaría algo así:

Hay que buscar la configuración del servidor de SonarQube dentro de Jenkins, está en “Administrar Jenkins > System > SonarQube Servers”.
En este apartado, hay que agregar los datos del servidor donde se encuentra SonarQube. Como en esta guía se esta manejando todo en local, en este caso sería 192.168.68.206:9000
o en su defecto, la IP local.
El campo “name” puede tener cualquier nombre, en mi caso le puse “sq1” este nombre se utiliza más adelante en el pipeline.
Hasta abajo hay un campo que se llama “Server authentication token” ahí selecciona el token que se guardó anteriormente en credenciales, perteneciente al de SonarQube.

Hasta este punto, hemos avanzado muchísimo. Deberías tomarte un break, porque aun falta crear las conexiones SSH al contenedor de Docker donde se van a desplegar las aplicaciones y el Pipeline de Jenkins, que es donde ocurre la magia.
Creación de Pipeline en Jenkins
Es hora de crear lo que hace funcionar todo este sistema, el famoso pipeline. Un pipeline es una secuencia automatizada de pasos para construir, probar y desplegar una aplicación. Facilita la integración continua (CI) y el despliegue continuo (CD) en el desarrollo de software.

Crear tarea en Jenkins
Desde la página principal de Jenkins, en el lado lateral, hay una opción llamada “Nueva tarea”

Se agrega el nombre del proyecto, normalmente el nombre del proyecto que se está desarrollando. También se selecciona el pipeline. Tal cual en la imagen. En esta guía este paso es manual, pero en futuras entregas este paso estará automatizado.

Luego de darle “Ok” podrás poner una pequeña descripción de la tarea.

Después de eso, podrás seleccionar algunos ajustes, para esta guía sólo se va a seleccionar “Desechar ejecuciones antiguas” y se pondrá como valores lo siguiente:

Ahora dirígete al final de de todo, podrás ver que hay un campo llamado “pipeline” y luego “definition”, deberá estar en “Pipeline script”, para poder crear el script.
Conforme a esta guía, se realizará lo siguiente en el pipeline:
- Obtiene el proyecto de GitHub
- Construye el proyecto
- Realiza la evaluación de Sonar
- Recibe la calificación del Quality Gate
- Se conecta al contenedor Docker
- Hace el deploy en el contenedor Docker
- Finaliza con mensaje de éxito
Esta es la plantilla que debes seguir, para únicamente crear los stages, que vendrían siendo los pasos que sigue el pipeline:
pipeline { agent any stages { } post { success { echo 'Proceso completado.' } } }
Stages del pipeline
1. Checkout
El primer paso es conectar con GitHub y bajar el proyecto. El script de abajo contempla la rama del repositorio, en mi caso lo voy a traer desde el “main”, además de pegar el la dirección SSH del proyecto y finalizando con ID de la credencial dada de alta en Jenkins.
Para encontrar la dirección SSH en el GitHub, basta con ir al repositorio e intentar bajarlo, ahí aparece la dirección SSH.

El primer stage quedaría así:
// STAGE CHECKOUT stage('Checkout') { steps { git branch: 'main', url: 'git@github.com:Tecsharp/Devdate.git', credentialsId: '3ceer4t5-e8b5-3424-8f59-hdur83d00e39' } } //
2. Build
La construcción del proyecto es muy sencilla, el script al final manda comandos bash, por lo que entender el pipeline se hace fácil. El segundo stage quedaría de la siguiente forma.
// STAGE BUILD stage('Build') { steps { sh '''#!/bin/bash ./mvnw clean package ''' } } //
3. Sonar Analysis
Algo muy importante, es que tus proyectos, a nivel raíz, debe tener agregado en el POM si es Maven, los plugins de sonar, además de los archivos wrapper de maven. Es muy fácil agregarlos.
Para los plugins:
/// PLUGINS SONAR <plugin> <groupId>org.sonarsource.scanner.maven</groupId> <artifactId>sonar-maven-plugin</artifactId> <version>3.9.1.2184</version> </plugin> ///
/// PLUGIN SUREFIRE <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M5</version> </plugin> ///
/// PLUGIN JACOCO PARA COBERTURA <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.8</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>prepare-package</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> ///
Ahora faltan los archivos wrapper en el proyecto, para eso nos dirigimos a la raíz del proyecto, donde está el “src” y ejecutamos el siguiente comando de Maven:
mvn wrapper:wrapper
Esto generará algunos archivos que tienen que estar en el repositorio, así que después de hacer eso, haz un push al repo de GitHub.
Si no los ves, revisa el el archivo .gitignore


Una vez que el proyecto está listo, quiero hacer una observación importante, en la linea 6, está recibiendo un valor “sq1” ese es el nombre que se le puso a la variable de entorno en Jenkins, para consultar puedes ir a:
“Configurar Jenkins > System > SonarQube servers”
// STAGE SONAR ANALYSYS stage('Sonar Analysis') { steps { script { withSonarQubeEnv('sq1') { sh '''#!/bin/bash ./mvnw sonar:sonar ''' } } } } //
Lo que recomiendo en este punto, es guardar el pipeline y ejecutarlo, para que así se cree de forma automática la carpeta del proyecto en SonarQube y recuperar un valor llamado “projectKey” que es requerido en el siguiente stage.
Desde la página principal de Jenkins, selecciona la tarea que recién creaste y después construyes el proyecto.


Si en este paso no descarga el proyecto de GitHub por problemas del known_hosts, problema que sucede cuando es una instancia de Docker en vez de una instalación en un servidor dedicado, intenta lo siguiente:
Recupera los hosts de la maquina con el siguiente comando:
cat ~/.ssh/known_hosts
Te debe arrojar algo como lo siguiente:
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTsdfsdfsdfsdfsdfG6UOoqKLsabgH5C9okWi0dh2l9GKJl
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7asdasdGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
Copia todo el código y dirígete a Jenkins:
“Administrar Jenkins > Security > Git Host Key Verification Configuration”
Y pega los hosts que se recuperaron. Además de utilizar la opción de “Manually provided keys”

Una vez hecho eso, puedes reintentar construir y ya te debería permitir avanzar.

4. Quality Gate
Este stage es uno de los más problemáticos, por sí mismo ya está listo para usarse, únicamente debes cambiar el token de Sonar, que anteriormente se sacó desde la aplicación de Sonar, si lo perdiste, no te preocupes, puedes volver a sacar otro en: http://localhost:9000/admin/users
Si realizaste el paso anterior, que fue construir el proyecto, ahora puedes dirigirte a SonarQube y abrir el proyecto.http://localhost:9000/

Ve a “Project information”.

Y en la parte izquierda podrás observar el “Project Key” copiamos esa información.

Además, la URL está compuesta de varias partes, primero por el URL de SonarQube, en este caso es 192.168.68.206, en el puerto 9000, pero también aparece un projectKey. Este project key se genera cuando se crea la carpeta del proyecto en Sonar y en esta guía, Jenkins crea la carpeta en Sonar.
Con el código del project key, sólo falta cambiar el token “squ_”, ajustar la dirección de SonarQube y cambiar el projectKey después del “=”.
// STAGE SONAR ANALYSYS stage('Quality Gate') { steps { script { sh 'apt-get update && apt-get install -y jq' def retries = 10 def delay = 30 def token = 'squ_d2253648d5dc7773f7005dc895691f516233fd0a' def url = "http://192.168.68.206:9000/api/qualitygates/project_status?projectKey=com.tecsharp.devdate.app:spring.boot.devdate.web" def status = null // Inicializar la variable status for (int i = 0; i < retries; i++) { def response = sh(script: "curl -s -u ${token}: ${url}", returnStdout: true).trim() status = sh(script: "echo '${response}' | jq -r '.projectStatus.status'", returnStdout: true).trim() if (status == 'OK') { echo "Quality Gate passed" break } else if (status == 'ERROR') { def conditions = sh(script: "echo '${response}' | jq -c '.projectStatus.conditions[]'", returnStdout: true).trim() echo "SonarQube Quality Gate failed. Conditions causing failure:" conditions.split('\n').each { condition -> def metricKey = sh(script: "echo '${condition}' | jq -r '.metricKey'", returnStdout: true).trim() def actualValue = sh(script: "echo '${condition}' | jq -r '.actualValue'", returnStdout: true).trim() def errorThreshold = sh(script: "echo '${condition}' | jq -r '.errorThreshold'", returnStdout: true).trim() def statusCondition = sh(script: "echo '${condition}' | jq -r '.status'", returnStdout: true).trim() echo "Metric: ${metricKey}, Actual Value: ${actualValue}, Error Threshold: ${errorThreshold}, Status: ${statusCondition}" } error "SonarQube Quality Gate failed with status: ${status}" } echo "Quality Gate status is ${status}. Retrying in ${delay} seconds..." sleep delay } if (status != 'OK') { error "El análisis de SonarQube no pasó el Quality Gate. Estado final: ${status}" } } } } //
Aquí pueden pasar varias cosas, que tu proyecto pase el QualityGate o que no pase o en el caso de que no tengas Jenkins en Docker, que necesites JQ.
Para usuarios no Docker:
sudo apt update && apt install -y jq
Realiza nuevamente la construcción en Jenkins, no debería fallar:

Ya casi terminamos, esto es mucho de ir probando también, porque no siempre vamos a tener los mismos resultados. Pero estoy intentando abarcar lo más posible.
5. Deploy
Esta parte es interesante, porque hay que crear un par de archivos para poder hacer que el contenedor de Jenkins se comunique de forma directa con el host y así poder levantar los contenedores de los despliegues.
Si vienes de mi publicación de como instalar Jenkins en Docker, puedes continuar sin problemas, porque ya tienes la carpeta compartida entre el contenedor y el host.
También puedes ver el tutorial y después continuar.
>> Ver “Instalar Jenkins en Docker Linux” <<
5.1. Dockerfile para el contenedor de Jenkins
Asegurate de que el archivo Dockerfile esté en la misma carpeta que el docker-compose.yml
.
El archivo Dockerfile tiene esta estructura:
// Dockerfile FROM jenkins/jenkins:lts # Install Docker CLI USER root RUN apt-get update && apt-get install -y \ apt-transport-https \ ca-certificates \ curl \ gnupg2 \ software-properties-common \ lsb-release \ && curl -fsSL https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/gpg | apt-key add -> && add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]') \ $(lsb_release -cs) \ stable" \ && apt-get update && apt-get install -y docker-ce-cli USER root //
5.3. Dockerfile para repositorio GitHub
Para que este paso funcione, se debe crear un archivo Dockerfile en la raíz del proyecto, es muy sencillo, sólo agrega el archivo Dockerfile sin ningún tipo de formato y agregas dentro el siguiente código:
# DOCKERFILE PARA PROYECTO FROM openjdk:17-jdk-slim WORKDIR /app COPY spring.boot.devdate.web-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8082 CMD ["java", "-jar", "app.jar"] #
Lo que hace ese archivo, es que ayuda a generar la imagen que se va a desplegar en el contenedor “devdate” y además le cambia el nombre a app.jar. Recuerda cambiar el JDK si estás utilizando uno más nuevo y el nombre del compilado que generó maven.
5.3. Stage Deploy
Para armar el stage de Deploy, quiero que entiendas como funciona el script.
1. Si no existe la carpeta, crea la carpeta que tú le indiques en la carpeta compartida /home/docker/apps
, en este caso es “devdate”
2. Copia el build que Jenkins hizo, en este caso lo toma desde el target que genera maven: target/spring.boot.devdate.web-0.0.1-SNAPSHOT.jar
y lo pega en la nueva carpeta “devdate” en la carpeta compartida.
3. Copia el archivo Dockerfile del proyecto que está alojado en GitHub y lo pega en la misma carpeta de “devdate” en la carpeta compartida.
4. Si existe un contenedor llamado “devdate” lo detiene.
5. Elimina el contenedor “devdate”.
6. Construye la imagen con el archivo Dockerfile que se copió en la carpeta compartida “devdate” este archivo es el mismo que está en el repositorio de GitHub.
7. Inicia el contenedor “devdate” en el puerto 8082.
Cambia los nombres de “devdate” por el nombre de tu proyecto y elige un puerto que no esté en uso dentro del host. Igual no olvides que el proyecto debe estar configurado también para que salga por el puerto 8082 o el puerto que indiques. Esto debe estar en algún archivo .properties de tu proyecto.
# STAGE DEPLOY stage('Docker Deploy') { steps { // Ejecutar los comandos Docker en el host a través del socket compartido sh ''' mkdir -p /home/docker/apps/devdate cp target/spring.boot.devdate.web-0.0.1-SNAPSHOT.jar /home/docker/apps/devdate/ cp Dockerfile /home/docker/apps/devdate/ docker stop devdate || true docker rm devdate || true docker build -t devdate /home/docker/apps/devdate/ docker run -d --name devdate -p 8082:8082 devdate ''' } } #
Después de esto, el pipeline debería estar funcionando completamente.

Y aquí la aplicación corriendo sobre la ip local y el puerto 8082.

Este pipeline está diseñado para que puedas desplegar seguidamente, únicamente subir el código al repositorio de GitHub y darle al botón de “Construir ahora”.
Lo malo
No todos los sistemas son perfecto y este se aleja de serlo. Principalmente porque aún se tiene que configurar por proyecto y no se ha activado el trigger para que se automatice todavía más.
1. Vas a tener que configurar un pipeline por cada proyecto
Esto creo que es lo más feo de todo, pero si sólo son proyectos de prueba, no veo ningún inconveniente.
2. Diferentes pipelines y configuraciones
Me sucedió que quise automatizar el despliegue de un proyecto frontend creado en Next.js y tuve que hacer unas configuraciones al Dockerfile de mi repositorio porque Jenkins no creaba la carpeta en SonarQube.
Lo bueno
1. Despliegue automatizado
Si eres como yo, que siempre quiere tener una versión más acercada a la de producción, sin tener que tener el servidor de Tomcat abierto en el IDE, automatizar esta parte te ahorra minutos y a un sólo botón.
2. Disponibilidad
Esto mismo hace que tengas un servidor dedicado a despliegues y disponibilidad, lo que permite que puedas acceder desde cualquier dispositivo y si tienes configurada la IP publica o un VPN, incluso puedes hacerlo desde fuera.
3. Aprendes Docker y Linux
Aunque esta guía la realicé intentando ser lo más claro posible, seguramente esto hará que investigues y profundices más en este mundo de los contenedores y encima agilizas el uso de comandos Linux.
Conclusión
Para esta guía hay muchas cosas que hay que tomar en cuenta. Una de ellas es que va a variar dependiendo de que es lo que estés buscando desplegar.
Como lo dije en los puntos malos, esto va a depender del tipo de proyecto, yo sólo estoy usando proyectos en Spring y en Next.js, por lo que para mi funciona de forma increíble.
De aquí en adelante es hasta donde queramos llegar. Todo es posible.