Tutoriales de VPS
Instalar Jenkins CI/CD en un VPS (2026): Docker, Caddy HTTPS, Webhook de GitHub, agentes y copias de seguridad
Despliega Jenkins LTS en un VPS con Docker Compose y Caddy, y configura Webhooks de GitHub, agentes WebSocket, pipelines con Jenkinsfile, refuerzo de permisos, copia y restauración, actualizaciones y resolución de problemas.

Después de subir el código a GitHub, todavía hay que entrar por SSH al servidor, ejecutar manualmente git pull, instalar dependencias, correr pruebas y reiniciar servicios. Con pocos proyectos este flujo se tolera. Cuando los proyectos se multiplican, el problema más común no es "no saber desplegar", sino un paso olvidado: pruebas sin ejecutar, variables de entorno equivocadas, una versión fallida que sobrescribe producción, y nadie sabe quién hizo qué.
Jenkins resuelve esta cadena de automatización. Puede obtener el código tras recibir un push de GitHub, ejecutar compilaciones, pruebas, archivado de artefactos y despliegue según el Jenkinsfile del repositorio, y guardar los registros de cada ejecución. Pero Jenkins también puede ejecutar scripts de compilación arbitrarios, así que el enfoque de despliegue no puede quedarse en "que funcione primero". El controlador, los agentes de compilación, las credenciales y los permisos de Docker deben separarse desde el primer día.
Este artículo usa como base Jenkins 2.568.2 LTS, Java 21, Docker Compose, Caddy y Ubuntu 24.04 para montar un entorno Jenkins CI/CD adecuado para equipos pequeños. Completarás el despliegue del controlador, HTTPS, el refuerzo inicial, los Webhooks de GitHub, los pipelines, los agentes WebSocket, la copia y restauración, las actualizaciones y la resolución de problemas habituales. La información de versiones se verificó en la página oficial de descargas de Jenkins en septiembre de 2026; en el momento real de la instalación conviene revisar primero el LTS más reciente y los avisos de seguridad.
Jenkins llama controlador al servicio central. Guarda configuración, credenciales, tareas y registros de compilación, y se encarga de encolar y despachar tareas a los agentes. La instalación por defecto permite ejecutar compilaciones en el nodo integrado, lo que es cómodo para probar funciones pero no sirve para uso a largo plazo.
Los scripts de compilación vienen del repositorio, pueden ejecutar shell, descargar dependencias, correr pruebas e incluso llamar a Docker. Mientras se ejecuten en el controlador, pueden leer $JENKINS_HOME, la configuración de plugins y archivos relacionados con credenciales. Jenkins recomienda oficialmente fijar el número de ejecutores del controlador en 0 y entregar todas las compilaciones a los agentes.
Este artículo usa la siguiente estructura:
GitHub Push / Pull Request
|
| HTTPS Webhook
v
Caddy :443
|
| 127.0.0.1:8080
v
Jenkins Controller
仅编排,不执行构建
^
| WebSocket over HTTPS
|
Jenkins Build Agent
拉代码、测试、构建
Los agentes WebSocket se conectan activamente al controlador y solo usan el 443 HTTPS existente, así que no hace falta exponer a internet el puerto TCP de agentes de Jenkins 50000.
La ventaja de Jenkins es su gran cantidad de plugins, la libertad de ubicación del despliegue, la capacidad de conectarse a redes internas y de gestionar agentes en distintos sistemas y arquitecturas. El coste es que hay que mantener uno mismo el controlador, los plugins, las credenciales, las copias de seguridad y las actualizaciones.
| Opción | Ventajas | Costes principales | Más adecuada para |
|---|---|---|---|
| Jenkins | Alta libertad de autohospedaje, ecosistema maduro de plugins y agentes | Requiere mantener controlador, plugins y políticas de seguridad | Varios entornos, compilaciones en red interna, experiencia previa con Jenkins |
| GitHub Actions | Integración directa con GitHub, runners alojados listos para usar | Dependencia profunda de GitHub, el autohospedaje complejo sigue requiriendo operación | Proyectos en GitHub y pipelines estándar |
| GitLab CI | Integrado con repositorios, permisos y artefactos de GitLab | El GitLab autohospedado consume bastantes recursos | Equipos que ya usan GitLab CE |
Si todo el código está en GitHub y el flujo no es complejo, los runners autohospedados de GitHub Actions suelen ahorrar tiempo de gestión. Si necesitas una plataforma de código autohospedada con issues, registry y CI integrados, consulta el tutorial de despliegue de GitLab CE y Runner. Cuando ya tienes varios sistemas internos, compilaciones multiplataforma o muchos jobs de Jenkins, el Jenkins autohospedado encaja mejor.
La documentación oficial de instalación con Docker de Jenkins recomienda para equipos pequeños más de 4 GB de RAM y más de 50 GB de disco. Es solo el punto de partida para el controlador y un uso ligero; lo que realmente consume recursos son la compilación, las pruebas, las imágenes Docker y los artefactos.
| Escenario | Recomendación para el controlador | Recomendación para agentes |
|---|---|---|
| Aprendizaje y proyectos personales | 2 vCPU, 4 GB de RAM, 50 GB SSD | Puede compartir máquina temporalmente, pero con contenedor y usuario separados |
| Equipos pequeños de 3–10 personas | 2–4 vCPU, 4–8 GB de RAM, 80 GB SSD | Empezar con una máquina separada de 4 vCPU y 8 GB de RAM |
| Compilaciones Java/Android/frontend grandes | Mantener el controlador estable | 8 vCPU, 16 GB o más, escalar según los picos de compilación |
| Multiarquitectura o alta concurrencia | Controlador dedicado | Varios agentes efímeros con etiquetas |
Si el controlador y los agentes comparten un VPS de 4 GB, la compilación puede empujar fácilmente la interfaz de administración a OOM. Con presupuesto limitado, al menos configura límites de memoria del contenedor y fija los ejecutores del agente en 1. Una opción más segura es un VPS pequeño y estable para el controlador y otra máquina reconstruible en cualquier momento para los agentes.
Tampoco mires solo el paquete de instalación de Jenkins al dimensionar el disco. Lo siguiente crecerá de forma continua:
- Configuración, plugins y registros de compilación en
$JENKINS_HOME; - Workspaces de agentes, cachés de dependencias y archivos temporales;
- Imágenes Docker, cachés de BuildKit y capas de contenedor;
- Artefactos archivados, informes de pruebas y logs de consola;
- Copias de seguridad locales.
Prepara un dominio dedicado, por ejemplo ci.example.com, y apunta su registro A al VPS del controlador. No añadas registros AAAA si IPv6 no está configurado correctamente.
El controlador solo necesita:
| Puerto | Uso | Recomendación |
|---|---|---|
22/TCP | Administración SSH | Restringir a IPs de administración, usar inicio de sesión con clave |
80/TCP | Validación de certificados y redirecciones de Caddy | Abrir a internet |
443/TCP | Web de Jenkins, Webhooks, agentes WebSocket | Abrir a los orígenes necesarios |
8080/TCP | HTTP del contenedor Jenkins | Enlazar solo a 127.0.0.1 |
50000/TCP | Agentes inbound tradicionales | No se usa en este artículo, no abrir |
GitHub.com necesita entregar Webhooks a Jenkins, así que 443 debe ser accesible desde internet. Si Jenkins solo sirve a una red interna, puedes prescindir de los Webhooks de GitHub y usar un túnel controlado, una GitHub App o SCM Poll programado; no expongas todo el panel de administración sin protección solo por un callback.
Si te encuentras con "el contenedor está bien pero el dominio no abre", sigue la guía de resolución de problemas de puertos, firewall, grupos de seguridad y direcciones de escucha para comprobar desde los puertos locales hacia fuera, en lugar de desactivar UFW de inmediato.
El siguiente ejemplo usa Ubuntu 24.04. Se recomienda instalar Docker desde el repositorio oficial de Docker; si ya tienes un entorno Docker, no ejecutes scripts de un clic de origen desconocido.
sudo apt update
sudo apt full-upgrade -y
sudo apt install -y ca-certificates curl caddy openssl jq
docker --version
docker compose version
sudo systemctl enable --now caddy
Los entornos de producción también necesitan rotación de logs, límites de recursos, comprobaciones de estado y rollback; revísalos con la lista de configuración de producción de Docker Compose.
sudo install -d -m 750 -o "$USER" -g "$USER" /opt/jenkins
cd /opt/jenkins
Este artículo usa un volumen con nombre de Docker para guardar $JENKINS_HOME. El UID del usuario de servicio en la imagen oficial de Jenkins suele ser 1000; los volúmenes con nombre reducen los problemas de permisos habituales de los bind mounts del host.
Crea /opt/jenkins/compose.yaml:
services:
jenkins:
image: jenkins/jenkins:2.568.2-lts-jdk21
container_name: jenkins-controller
restart: unless-stopped
environment:
JAVA_OPTS: >-
-Djava.awt.headless=true
-Djenkins.install.runSetupWizard=true
JENKINS_OPTS: >-
--httpPort=8080
ports:
- "127.0.0.1:8080:8080"
volumes:
- jenkins_home:/var/jenkins_home
mem_limit: 3g
cpus: 2.0
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/login >/dev/null || exit 1"]
interval: 30s
timeout: 10s
retries: 10
start_period: 90s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
volumes:
jenkins_home:
name: jenkins_home
Aquí deliberadamente no se mapea 50000 ni se monta /var/run/docker.sock en el controlador. Entregar el socket de Docker al controlador equivale a dar privilegios de host a quien pueda controlar las compilaciones; más adelante moveremos las compilaciones a un agente separado.
mem_limit: 3g es un punto de partida para un VPS con más de 4 GB que solo ejecuta el controlador. Si la misma máquina también tiene bases de datos u otras aplicaciones, reasigna la memoria según el uso real; no copies los números y dejes que el sistema use swap de forma prolongada.
Primero revisa la configuración final:
cd /opt/jenkins
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
El primer arranque debe descomprimir Jenkins, inicializar directorios y cargar metadatos de plugins, así que la comprobación de estado puede tardar uno o dos minutos. Consulta los logs:
docker compose logs -f --tail=200 jenkins
Cuando aparezca el mensaje de que Jenkins ha terminado de arrancar, pulsa Ctrl+C para salir de los logs. Confirma que el puerto solo está enlazado a la dirección de loopback:
curl -fsSI http://127.0.0.1:8080/login
ss -lnt | grep ':8080'
La dirección de escucha esperada es 127.0.0.1:8080, no 0.0.0.0:8080.
Crea o edita /etc/caddy/Caddyfile:
ci.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8080
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
Caddy pasa el Host por defecto y establece X-Forwarded-For, X-Forwarded-Proto y X-Forwarded-Host. Esta es exactamente la información que Jenkins necesita para generar redirecciones HTTPS y direcciones de callback correctas.
Comprueba y recarga:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager
curl -fsSI https://ci.example.com/login
Poner Jenkins en la ruta raíz de un subdominio dedicado es lo más sencillo. Si de verdad necesitas usar https://example.com/jenkins/, el controlador y el proxy inverso deben usar exactamente el mismo context path, y debes añadir --prefix=/jenkins para Jenkins. Cambiar solo la ruta en Caddy sin cambiar Jenkins suele provocar 404 en recursos estáticos, redirecciones a URLs incorrectas o avisos del backend de proxy inverso roto. Para más configuración de proxy inverso, consulta el tutorial de HTTPS automático con Caddy.
Al visitar por primera vez https://ci.example.com/ verás la página Unlock Jenkins. Lee la contraseña de un solo uso:
cd /opt/jenkins
docker compose exec jenkins \
cat /var/jenkins_home/secrets/initialAdminPassword
Tras pegar la contraseña, instala los plugins sugeridos y crea un nuevo administrador personal. No uses a largo plazo un admin compartido ni guardes la contraseña inicial en el chat del equipo.
Tras completar el asistente inicial, ve a Manage Jenkins → System y configura Jenkins URL como:
https://ci.example.com/
Esta dirección se usa para Webhooks, correo y enlaces de página. Debe coincidir con la dirección HTTPS que los usuarios visitan realmente, o el backend puede avisar It appears that your reverse proxy setup is broken.
Ve a:
Manage Jenkins → Nodes → Built-In Node → Configure
Configura Number of executors en 0 y guarda. Las tareas sin agente se quedarán en la cola; es normal. Termina la configuración del agente antes de empezar compilaciones reales.
Ve a Manage Jenkins → Security:
- Mantén la autenticación activada; no permitas que usuarios anónimos tengan permisos de administración o configuración de jobs;
- Incluso en equipos pequeños, no uses
Logged-in users can do anythingcomo estrategia de autorización a largo plazo; - Al instalar y configurar Matrix Authorization Strategy, empieza por el mínimo privilegio;
- Solo unos pocos administradores deben tener
Overall/Administer; - Restringe
Credentials/Createy los permisos de configuración de jobs; - Mantén Agent → Controller Access Control activado; no lo desactives solo para silenciar errores de plugins antiguos.
Si el panel de administración debe ser accesible públicamente, añade VPN, proxy de identidad o restricción por IP fija delante de Caddy. Cuando los Webhooks y la interfaz de administración comparten dominio, prueba las reglas de restricción por separado para no bloquear también /github-webhook/.
Cuantos más plugins, más complejas son las dependencias y las actualizaciones de seguridad. Para empezar normalmente solo necesitas:
- Pipeline;
- Git;
- GitHub o GitHub Branch Source;
- Credentials Binding;
- Matrix Authorization Strategy;
- Docker Pipeline, si vas a usarlo en agentes.
Tras instalar, ve a Manage Jenkins → Plugins para revisar actualizaciones y avisos de seguridad. No instales plugins sin mantenimiento durante años solo por copiar un tutorial antiguo.
Los agentes pueden estar en otro VPS o, temporalmente, en un contenedor separado en el mismo host que el controlador. El enfoque de una sola máquina no ofrece alta disponibilidad, pero al menos evita que los procesos de compilación lean directamente jenkins_home.
En Jenkins, ve a:
Manage Jenkins → Nodes → New Node
Crea un Permanent Agent, por ejemplo:
- Node name:
linux-agent-01 - Number of executors:
1 - Remote root directory:
/home/jenkins/agent - Labels:
linux docker-small - Usage: Only build jobs with label expressions matching this node
- Launch method: Launch agent by connecting it to the controller
- WebSocket: activado
Tras guardar, Jenkins muestra el comando de conexión y el Agent Secret. El secret equivale a la credencial de inicio de sesión del agente; no lo escribas en Git.
Crea .agent.env en /opt/jenkins:
cd /opt/jenkins
umask 077
cat > .agent.env <<'EOF'
JENKINS_URL=https://ci.example.com/
JENKINS_AGENT_NAME=linux-agent-01
JENKINS_AGENT_SECRET=把这里替换为节点页面显示的真实Secret
EOF
chmod 600 .agent.env
Añade el agente bajo services en compose.yaml. Aquí se usa la imagen oficial de agente inbound con JDK 21:
agent:
image: jenkins/inbound-agent:jdk21
container_name: jenkins-agent-01
restart: unless-stopped
env_file:
- .agent.env
command:
- -url
- ${JENKINS_URL}
- -secret
- ${JENKINS_AGENT_SECRET}
- -name
- ${JENKINS_AGENT_NAME}
- -webSocket
- -workDir
- /home/jenkins/agent
volumes:
- jenkins_agent_work:/home/jenkins/agent
mem_limit: 3g
cpus: 2.0
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
También añade lo siguiente al final del archivo bajo volumes:
jenkins_agent_work:
name: jenkins_agent_work
Hay un problema fácil de pasar por alto: Compose por defecto solo lee .env para la expansión de ${变量}, mientras que env_file solo pone variables en el contenedor. Lo más sencillo es cambiar command a URLs y nombres de nodo fijos no sensibles, y expandir solo el secret desde .env; o especificarlo explícitamente al arrancar:
cd /opt/jenkins
docker compose --env-file .agent.env config --quiet
docker compose --env-file .agent.env up -d agent
docker compose logs --tail=100 agent
Confirma en la página del nodo de Jenkins que el estado es Connected. Las conexiones WebSocket pasan por https://ci.example.com/ y no necesitan 50000.
jenkins/inbound-agent:jdk21 proporciona principalmente el entorno de ejecución del agente y no incluye automáticamente Node.js, Maven, Go, el SDK de Android ni el CLI de Docker. Crea tu propia imagen de agente con versiones fijas para las herramientas que necesite tu proyecto. Por ejemplo:
FROM jenkins/inbound-agent:jdk21
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends git curl make \
&& rm -rf /var/lib/apt/lists/*
USER jenkins
No dejes que los agentes se ejecuten como root de forma permanente. Cada cambio de imagen debe pasar por revisión de código, y registra tags o digests de las imágenes base.
Muchos tutoriales añaden esta línea a los agentes:
- /var/run/docker.sock:/var/run/docker.sock
Esto permite ejecutar docker build de inmediato, pero una compilación con acceso al socket de Docker equivale básicamente a tener privilegios de root en el host. Cualquiera que pueda modificar el Jenkinsfile podría montar el directorio raíz del host, leer secretos de otros contenedores o controlar el controlador.
Opciones más seguras:
- Usar un VPS de agente dedicado y reconstruible que no ejecute nada más;
- Usar máquinas virtuales de corta duración o agentes de pod de Kubernetes;
- Usar un BuildKit/Docker Builder remoto protegido con TLS;
- Usar agentes totalmente aislados y sin credenciales de producción para pull requests no confiables;
- Separar el despliegue de producción y las pruebas ordinarias en agentes, etiquetas y ámbitos de credenciales distintos.
Si es solo un proyecto personal y aceptas el riesgo del socket de Docker en la misma máquina, deja claro igualmente que este agente es una máquina de compilación desechable que no ejecuta bases de datos, gestores de contraseñas ni otros servicios críticos.
Las credenciales de Jenkins no deben escribirse en scripts de pipeline ni en parámetros de jobs. Ve a:
Manage Jenkins → Credentials
Elige según el propósito:
- Obtener repositorios privados: deploy key o GitHub App con mínimo privilegio;
- Llamar a la API de GitHub: GitHub App o PAT fine-grained con mínimo privilegio;
- Servidores de despliegue: clave SSH dedicada;
- Registry: cuenta de bot dedicada o token de corta duración.
Limita las credenciales a carpetas concretas siempre que sea posible en lugar de hacerlo todo global. Jenkins recomienda oficialmente empezar por "sin permisos por defecto" y dar acceso solo a los proyectos y personas que realmente lo necesitan.
Los pipelines referencian secretos por ID de credencial y nunca leen el valor real. Por ejemplo:
withCredentials([string(credentialsId: 'registry-token', variable: 'TOKEN')]) {
sh '''
set +x
./scripts/publish.sh "$TOKEN"
'''
}
Usar comillas simples triples en Groovy permite que el shell expanda variables en tiempo de ejecución, reduciendo el riesgo de que los secretos aparezcan en argumentos de proceso o logs tras la interpolación de Groovy. Aunque Jenkins intenta enmascararlos, nunca pongas pipelines no confiables y credenciales de producción en el mismo ámbito de permisos.
Tras instalar el plugin de GitHub, la URL habitual del Webhook es:
https://ci.example.com/github-webhook/
No olvides la barra final. Ve al repositorio de GitHub:
Settings → Webhooks → Add webhook
Rellena:
- Payload URL:
https://ci.example.com/github-webhook/ - Content type:
application/json - Secret: genera un valor aleatorio independiente y usa el mismo valor en la configuración correspondiente de GitHub Server/Webhook en Jenkins
- Events: selecciona solo los eventos Push, Pull request y otros que necesite la tarea
- Active: activado
Genera el secret:
openssl rand -hex 32
No reutilices la contraseña de administrador, el PAT de GitHub ni el secret del agente. El secret del Webhook sirve para verificar que las solicitudes vienen de quien posee ese secret y no debe otorgar permisos de la API de GitHub.
En el job, activa GitHub hook trigger for GITScm polling; en Multibranch Pipeline, configura GitHub Branch Source para que el plugin escanee ramas y pull requests.
En la página del Webhook de GitHub, haz clic en Recent Deliveries para revisar:
- El código de estado HTTP;
- El tipo de evento y el Delivery ID en Request Headers;
- El contenido de Response;
- Si se disparó el job correspondiente de Jenkins.
Recibir HTTP 200 solo significa que el endpoint aceptó la solicitud, no que el pipeline se haya ejecutado. Revisa también la URL del repositorio, las reglas de ramas, el trigger del job y los logs del sistema de Jenkins.
Se recomienda escribir el pipeline en Jenkinsfile en la raíz del repositorio en lugar de guardarlo solo en la interfaz web de Jenkins. Así el proceso de compilación sigue la revisión de código y el historial de versiones.
El siguiente es un ejemplo mínimo que no toca directamente servidores de producción:
pipeline {
agent { label 'linux' }
options {
timestamps()
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '30'))
timeout(time: 20, unit: 'MINUTES')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Environment') {
steps {
sh '''
set -eu
uname -a
git --version
'''
}
}
stage('Test') {
steps {
sh '''
set -eu
./scripts/ci-test.sh
'''
}
}
stage('Package') {
steps {
sh '''
set -eu
./scripts/ci-package.sh
'''
}
}
}
post {
always {
junit allowEmptyResults: true, testResults: 'reports/**/*.xml'
archiveArtifacts allowEmptyArchive: true, artifacts: 'dist/**'
}
cleanup {
deleteDir()
}
}
}
Pon los comandos específicos de lenguaje y framework en scripts/ci-test.sh y scripts/ci-package.sh del repositorio, para que se ejecuten tanto en local como en Jenkins; resolver problemas es más fácil que meter decenas de líneas de shell en Groovy.
Crea un Pipeline o Multibranch Pipeline, elige Pipeline script from SCM y rellena el repositorio y la ruta de Jenkinsfile. En la primera ejecución, confirma que la tarea aparece en linux-agent-01, no en el Built-In Node.
Tras pasar pruebas y empaquetado, mucha gente hace directamente:
ssh root@production "cd /app && git pull && docker compose up -d"
Este comando es simple, pero entrega la clave privada de root al entorno de compilación, y el rollback y la auditoría son débiles. Un estándar mínimo mejor es:
- Crear un usuario de sistema dedicado al despliegue, sin inicio de sesión por contraseña;
- Guardar la clave SSH solo en una credencial de Jenkins de una carpeta concreta;
- Restringir los comandos y directorios a los que puede acceder el usuario de despliegue;
- Compilar primero artefactos con números de versión o digests inmutables;
- Desplegar una versión explícita, no el
git pullen producción; - Detener y revertir automáticamente si falla la comprobación de estado;
- Requerir aprobación para despliegues de producción o permitir solo ramas protegidas.
No dejes que los pull requests de colaboradores externos toquen credenciales de despliegue. Los agentes de prueba de pull requests y los agentes de despliegue de producción deben usar etiquetas, redes y ámbitos de credenciales distintos.
Los jobs de Jenkins pueden conservar un número limitado de registros de compilación mediante buildDiscarder, pero las cachés de Docker y los workspaces de agentes siguen creciendo.
Revisa semanalmente:
df -h
docker system df
docker volume ls
docker compose logs --tail=100 jenkins
No ejecutes docker system prune -a --volumes sin confirmar para qué sirve. Puede borrar imágenes, cachés y volúmenes no montados que otros proyectos aún necesitan. Recomendaciones:
- Configurar retención de compilaciones por job;
- Subir artefactos grandes a almacenamiento de objetos o a un repositorio de artefactos;
- Usar discos dedicados para agentes;
- Configurar una política controlada de limpieza de caché para Docker BuildKit;
- Alertar por separado sobre
$JENKINS_HOMEy los workspaces de agentes; - No acumular copias locales en el mismo disco del sistema a largo plazo.
La configuración, los plugins, los jobs y los registros de compilación de Jenkins están principalmente en $JENKINS_HOME, que en este artículo corresponde al volumen con nombre jenkins_home. Cuando los requisitos de consistencia son altos, lo oficial recomienda instantáneas del sistema de archivos o del almacenamiento. Los equipos pequeños también pueden detener el controlador en una ventana de mantenimiento y crear un archivo.
Crea el directorio:
sudo install -d -m 700 -o "$USER" -g "$USER" /opt/jenkins/backups
sudo install -d -m 700 -o "$USER" -g "$USER" /opt/jenkins/controller-key
Primero entra en Quiet Down y espera a que terminen las compilaciones actuales:
Manage Jenkins → Prepare for Shutdown
Detén el controlador y archiva el volumen:
cd /opt/jenkins
docker compose stop jenkins
docker run --rm \
-v jenkins_home:/data:ro \
-v /opt/jenkins/backups:/backup \
alpine:3.22 \
tar --exclude='./workspace' \
--exclude='./caches' \
--exclude='./secrets/master.key' \
-czf "/backup/jenkins-home-$(date +%F-%H%M%S).tar.gz" \
-C /data .
docker compose start jenkins
docker compose ps
Jenkins advierte especialmente que master.key no debe guardarse junto con las copias de seguridad habituales. Quien tenga la copia habitual y la clave del controlador puede descifrar las credenciales guardadas por Jenkins. Copia la clave por separado a otro almacén cifrado estrictamente controlado:
docker run --rm \
-v jenkins_home:/data:ro \
-v /opt/jenkins/controller-key:/key \
alpine:3.22 \
sh -c 'umask 077; cp /data/secrets/master.key /key/master.key'
chmod 600 /opt/jenkins/controller-key/master.key
Tras copiarla a una bóveda de contraseñas offline o a un almacenamiento cifrado independiente, no subas la clave del controlador y los archivos habituales al mismo bucket ni al mismo ámbito de credenciales. Las copias habituales también contienen configuración sensible y deben cifrarse.
Además del volumen, también debes respaldar:
/opt/jenkins/compose.yaml;- El Caddyfile;
- Los Dockerfiles de agentes personalizados;
- La lista de plugins y la versión de la imagen de Jenkins;
- Una copia cifrada de
.agent.envo el proceso para volver a registrar agentes; - Los pasos de restauración y el resultado del último simulacro.
Tras la copia, sincroniza a otra máquina o a almacenamiento de objetos. Para restic, rclone, políticas de retención y simulacros de restauración concretos, consulta la guía de simulacros de copia y restauración en VPS.
No esperes a que falle el disco del controlador para descomprimir una copia por primera vez. Haz simulacros al menos cada trimestre en un entorno aislado:
- Crear un volumen vacío;
- Descomprimir
jenkins-home-*.tar.gz; - Restaurar
secrets/master.keydesde una ubicación segura independiente; - Arrancar con la misma imagen de Jenkins y versiones de plugins que la copia;
- Iniciar sesión y revisar jobs, carpetas, metadatos de credenciales y nodos;
- Ejecutar un pipeline sin permisos de producción contra un repositorio de prueba;
- Verificar Webhooks, agentes y archivado de artefactos;
- Registrar el objetivo de tiempo de recuperación y los archivos faltantes.
No aproveches la restauración para actualizar Jenkins y todos los plugins. El objetivo de la recuperación ante desastres es restaurar primero el estado original; la actualización de versiones debe ser otro cambio con copia de seguridad y pruebas.
Jenkins LTS actualiza su línea base y sus correcciones periódicamente. Antes de actualizar:
- Leer el changelog de LTS, la guía de actualización y los avisos de seguridad;
- Respaldar
$JENKINS_HOMEy la clave del controlador por separado; - Exportar los plugins instalados y sus versiones;
- Probar el arranque del controlador y los pipelines clave en una copia de la copia de seguridad;
- Revisar la versión de Java y las versiones mínimas de Jenkins de los plugins;
- Conservar la imagen antigua y los pasos completos de rollback.
Lista los plugins:
cd /opt/jenkins
docker compose exec jenkins \
jenkins-plugin-cli --list
Tras confirmar la nueva versión, cambia compose.yaml a un tag LTS explícito; no uses un latest no rastreable en la configuración de producción. Luego:
cd /opt/jenkins
docker compose pull jenkins
docker compose up -d jenkins
docker compose logs -f --tail=200 jenkins
Limpia las imágenes antiguas solo después de verificar el inicio de sesión, la reconexión de agentes, los Webhooks y los pipelines de prueba. Las actualizaciones de plugins también deben hacerse por lotes; actualizar decenas de plugins a la vez dificulta identificar qué dependencia causó el fallo.
cd /opt/jenkins
docker compose ps
docker compose logs --tail=200 jenkins
curl -fsSI http://127.0.0.1:8080/login
ss -lnt | grep ':8080'
Si el 127.0.0.1:8080 local tampoco funciona, soluciona primero el contenedor de Jenkins, la memoria o los permisos del volumen; si local funciona, revisa Caddy, DNS y el firewall.
Comprueba si Manage Jenkins → System → Jenkins URL es la dirección HTTPS externa y confirma que Caddy pasa correctamente el Host y X-Forwarded-*. Si usas una subruta, el --prefix del controlador y la ruta de Caddy deben coincidir.
Revisa en orden:
- El tipo de evento en GitHub Recent Deliveries;
- Si la Payload URL termina en
/github-webhook/; - Si el job tiene activado GitHub Hook Trigger;
- Si la URL del repositorio del job coincide con el repositorio del payload;
- Las reglas de descubrimiento de ramas de Multibranch;
- El plugin de GitHub y los logs del sistema;
- Si la cola está esperando porque no hay un agente coincidente.
Revisa los logs del agente:
cd /opt/jenkins
docker compose --env-file .agent.env logs --tail=200 agent
curl -fsSI https://ci.example.com/login
Causas habituales: sufijo de ruta incorrecto en la URL, nombre o secret del agente actualizados, Caddy sin soporte para conexiones WebSocket normales, cadena de certificados no confiada por el agente y hora del sistema incorrecta. Al recrear un nodo, el secret antiguo deja de ser válido, así que debes actualizar .agent.env.
Confirma que el agente está Connected, que las etiquetas coinciden con el agent { label 'linux' } del Jenkinsfile y que los ejecutores del agente no son 0. Mantener los ejecutores del controlador en 0 es la configuración correcta; no vuelvas a ejecutar compilaciones en el controlador solo para que desaparezca la cola.
La imagen de agente inbound no es un entorno de compilación completo. Entra en el agente y comprueba:
docker compose --env-file .agent.env exec agent sh
java -version
git --version
Las herramientas que falten deben añadirse a un Dockerfile de agente versionado y reconstruir la imagen; no instales manualmente en el contenedor en ejecución cada vez, porque los cambios desaparecen al reconstruir.
docker run --rm -v jenkins_home:/data alpine:3.22 \
sh -c 'ls -ldn /data; find /data -maxdepth 1 -printf "%u:%g %p\n" | head'
La imagen oficial suele ejecutarse con UID 1000. No ejecutes directamente chmod -R 777, ya que haría que credenciales y configuración fueran legibles por otros procesos. Primero averigua si un contenedor de copia ejecutado como root, un bind mount incorrecto o una copia manual cambiaron el propietario, y luego corrígelo con el UID/GID exacto.
df -h
docker system df
docker run --rm -v jenkins_home:/data:ro alpine:3.22 \
du -h -d 2 /data | sort -h | tail -30
Fíjate en los registros de compilación de jobs, artefactos archivados, workspaces, cachés de plugins y capas Docker de agentes. Confirma los requisitos de retención antes de borrar y no vacíes el volumen jenkins_home como si fuera una caché.
- Jenkins usa tags explícitos de LTS y JDK 21;
-
8080solo está enlazado a127.0.0.1; -
50000no está expuesto sin propósito; - Caddy HTTPS, Jenkins URL y dominio externo coinciden;
- La contraseña inicial de administrador ya se ha sustituido;
- Los ejecutores del nodo integrado son
0; - Las compilaciones solo se ejecutan en agentes con etiquetas explícitas;
- El controlador no monta el socket de Docker;
- Los secrets de agentes y los permisos de
.agent.envestán controlados; - Los usuarios anónimos y los usuarios normales no tienen permisos excesivos;
- El número de plugins está controlado y los avisos de seguridad están resueltos;
- Los Webhooks de GitHub solo se suscriben a los eventos necesarios;
- Las credenciales de GitHub, registry y despliegue usan mínimo privilegio;
- Los pull requests no confiables no pueden acceder a credenciales de producción;
- Jobs, artefactos y cachés de Docker tienen políticas de retención;
-
$JENKINS_HOMEtiene copias cifradas en otra máquina; -
master.keyse guarda separado de las copias habituales; - El proceso de restauración se ha probado en un entorno aislado;
- Antes de actualizar se revisan la guía de actualización LTS y la compatibilidad de plugins.
Jenkins recomienda oficialmente más de 4 GB de RAM y más de 50 GB de disco para equipos pequeños. Cuando el controlador solo orquesta, 2–4 vCPU y 4 GB de RAM pueden servir de punto de partida; la compilación, las pruebas y las compilaciones Docker deben ejecutarse en agentes separados. Si el controlador y los agentes comparten máquina, se recomiendan al menos 8 GB y limitar la concurrencia de agentes.
No. Los agentes inbound pueden conectarse por WebSocket a través de la dirección HTTPS existente de Jenkins, sin necesidad de habilitar un puerto TCP de agente adicional. Este artículo usa ese método. Solo si usas explícitamente el protocolo TCP de agente tradicional necesitas evaluar un puerto fijo y reglas de firewall.
Técnicamente sí, pero es muy arriesgado. Un proceso que controla el socket de Docker normalmente puede obtener capacidades de root en el host, y el controlador también guarda configuración y credenciales. Como mínimo, mueve las compilaciones Docker a agentes separados; en producción, prioriza agentes efímeros, VPS de compilación dedicados o builders remotos.
La dirección habitual del plugin de GitHub es https://你的Jenkins域名/github-webhook/; ojo con la barra final. Después de que GitHub devuelva 200, aún debes confirmar el tipo de evento, la coincidencia de repositorio, el trigger del job, las reglas de ramas y la cola de agentes; no te fíes solo del estado HTTP.
No del todo. Las copias habituales de $JENKINS_HOME deben cifrarse y guardarse en otra máquina, y secrets/master.key debe guardarse por separado en un dominio de seguridad distinto según lo recomendado oficialmente. También debes conservar el Compose, el Caddyfile, las imágenes de agentes personalizadas, las versiones de plugins y los pasos de restauración, y realizar simulacros de restauración reales.
Cuando todo el código está en GitHub y los pipelines son bastante estándar, GitHub Actions suele requerir menos mantenimiento. Si ya autohospedas GitLab, prioriza GitLab CI. Cuando necesitas acceso a red interna, agentes multisistema, plugins complejos o migrar jobs existentes, la flexibilidad de Jenkins vale más, pero debes aceptar el coste operativo continuo de plugins, seguridad y copias de seguridad.
- Descargas de Jenkins y versiones LTS
- Instalación oficial de Jenkins con Docker
- Aislamiento del controlador de Jenkins
- Guía de uso de agentes de Jenkins
- Puertos de red de Jenkins y agentes WebSocket
- Plugin de GitHub y Webhooks de Jenkins
- Seguridad de credenciales en Jenkins
- Copia y restauración de Jenkins
- Resolución de problemas de proxy inverso en Jenkins
El verdadero criterio de salida a producción de Jenkins no es que la página de inicio abra, sino que el controlador no ejecute compilaciones, los permisos de los agentes sean controlables, los Webhooks disparen con precisión, las credenciales no acaben en los logs, las copias se puedan restaurar y una actualización fallida pueda volver al estado original. Primero prueba commits, pruebas, artefactos y notificaciones de fallo con un repositorio de prueba sin permisos de producción, y luego conecta el despliegue real; es mucho más seguro que darle claves root a Jenkins desde el principio.