Deploy desde GitHub
Clona, construye y publica un repo de GitHub en el VPS; redeploy en cada push, historial por fases y vuelta atrás.
- Categoría: Servidores e infraestructura
- Estado: Disponible
- Requisitos: Un VPS activo de la empresa con la función
deploy_githubdisponible; La cuenta de GitHub de quien vincula, conectada a ArduMaker con acceso al repo; Ser miembro de la empresa con acceso a ese VPS (en Git un miembro hace lo mismo que un admin) - Herramientas MCP (Solo leen):
listar_repos_github_vps,ver_repo_github_vps,listar_commits_repo_github_vps,ver_commit_repo_github_vps,listar_logs_repo_github_vps,ver_log_repo_github_vps,ver_asistente_git_app,ver_config_deploy_app,ver_propietarios_github,ver_operacion_app - Herramientas MCP (Escriben o modifican):
instalar_app,vincular_repo_github_vps,crear_repo_github_vps,editar_repo_github_vps,redeploy_repo_github,guardar_config_deploy_app,controlar_app - Herramientas MCP (Destructivas):
eliminar_repo_github_vps,desinstalar_app,publicar_repo_en_dominio
¿Qué permite hacer?
El deploy de Git lleva un repositorio de GitHub a un VPS y lo mantiene al día: lo clona, lo construye, lo deja corriendo como servicio de systemd (o contenedor), lo publica con HTTPS y lo vuelve a desplegar en cada push a la rama elegida.
- Detectar qué es el repo (Node, Django, Python, sitio estático, Docker, Procfile) y proponer comandos de build y arranque.
- Declarar en un documento de deploy las fases (
pre_pull,install,build,release), los procesos (servicios, temporizadores, trabajos puntuales, estáticos, Docker, TCP), las variables de entorno y la publicación. - Desplegar automáticamente con cada push, a mano, o un commit puntual de la rama.
- Volver al commit de un deploy anterior que terminó bien.
- Ver el historial de deploys con cada fase, sus tiempos y los comandos con su salida (con los secretos tapados).
- Crear un repositorio nuevo en GitHub desde ArduMaker y desplegarlo.
¿Cuándo conviene usarlo?
- Una web, una API o un worker que vive en GitHub y tiene que quedar siempre igual a la rama principal.
- Tareas programadas del proyecto (un
timer) que tienen que viajar con el código. - Un equipo que quiere ver qué commit está en producción y poder volver atrás sin entrar al servidor.
¿Cómo funciona en ArduMaker?
1. Vincular y clonar
Hay dos puertas. La recomendada es instalarlo como app (instalar_app con app_type: git, repo_id, branch y, si se quiere, port y el documento deploy): queda en el inventario de Apps y la operación es durable. La otra es la pestaña GitHub del VPS (vincular_repo_github_vps), que solo clona y corre en segundo plano.
Al vincular, ArduMaker crea en GitHub un webhook con un secreto propio del repo: cada entrega llega firmada con HMAC-SHA256 y sin nada secreto en la URL. El repo se clona en <carpeta-del-deploy>. La URL con el token de GitHub queda en la carpeta .git, que es de root y no la puede leer la app.
2. Detectar y configurar
ver_asistente_git_app lista los repos que la cuenta de GitHub puede leer, sus ramas y, con repo y rama, lo que detecta leyendo los archivos sin ejecutar nada: framework, comandos de build y arranque y un puerto libre del VPS. Los formatos conocidos son Docker (también Compose), Procfile, Node web, Django, sitio estático de Vite, Python web y manual. Una migración propuesta siempre llega sin confirmar: el documento no valida hasta que una persona la confirma.
Lo que corre cada deploy es siempre un documento: el guardado con guardar_config_deploy_app, o, si el repo nunca guardó uno, el derivado de sus scripts de siempre (pre-pull, post-pull y servicios). Guardar un documento migra el repo: desde ahí manda el documento.
3. Desplegar por fases
Cada deploy corre estas fases en orden y las anota en el historial. Si una falla, las siguientes no corren y el repo queda en error con un texto corto (Deploy failed at the <fase> phase), nunca la salida cruda de un comando.
| Fase | Qué hace |
|---|---|
preparar | Usuario de Linux de la app, carpeta de la plataforma, archivos de variables, Docker si hace falta |
pre_pull | phases.pre_pull en la carpeta del clon |
pull | Trae la rama (o deja la carpeta en el commit pedido) |
install | phases.install, paso largo |
build | phases.build, paso largo; después los contenedores de los procesos docker |
release | La migración, solo si está confirmada |
procesos | Escribe o reinicia unidades, temporizadores y trabajos puntuales |
publicar | Server block del dominio en el nginx del VPS, si el perfil publica |
salud | Verifica que la app conteste o que las unidades sigan vivas |
Con la app instalada en Apps, el deploy lo corre el runner de operaciones y se retoma si el worker se cae. Un repo sin app corre el mismo deploy en segundo plano. Varios push seguidos se juntan: como mucho un deploy corriendo y uno en cola por app.
¿Qué cambia realmente?
<pk> es el número del repo en ArduMaker. Con policies.run_as: "root" no se crea el usuario y todo es de root.
| Qué | Dónde | Dueño y modo |
|---|---|---|
| Usuario de la app | ardu-app<pk>, de sistema, sin login | — |
| Clon | <carpeta-del-deploy> | del usuario; .git de root |
| Carpeta de la plataforma | /etc/ardumaker/deploy/ardu-repo<pk>/ | root:ardu-app<pk> 0750 |
| Variables de ejecución y de build | …/run.env, …/build.env | ardu-app<pk> 0600 |
| Guiones de cada paso y su salida | …/install.sh, build.sh, release.sh, <paso>.log | 0750 / root |
| Servicio principal | /etc/systemd/system/ardu-repo<pk>.service | root 0644 |
| Otros procesos y temporizadores | ardu-repo<pk>-<proceso>.service / .timer | root 0644 |
| Contenedores | ardu-repo<pk>, ardu-repo<pk>-<proceso> | — |
| Server block | /etc/nginx/sites-available/<dominio>.conf | — |
Una unidad que no cambió solo se reinicia; una que cambió se reescribe. Una unidad adoptada (de un repo de antes) se reinicia y nunca se escribe ni se borra. Lo que un deploy creó y el documento ya no tiene se borra en el deploy siguiente.
El documento de deploy
Es un JSON versionado (schema_version: 2). Se lee con ver_config_deploy_app y se reemplaza entero con guardar_config_deploy_app (apply=true lo aplica ya con un reconfigure).
| Parte | Valores |
|---|---|
profile | static, web, worker, scheduled, job, docker, network y code (solo código: en cada push trae el repositorio y nada más; sin instalar, compilar, migrar, procesos ni publicación) |
phases | pre_pull, install, build y release (command, confirmed, on_update) |
processes | Hasta 20, de tipo service, timer (con schedule), oneoff, static, docker o tcp; puertos de 1024 a 65535, sin repetir |
network.publish.mode | none, auto_subdomain, domain, port |
network.health.type | http, unit, timer, static |
env | Lista de {name, value} con scope build, run o ambos |
policies | build_timeout_s 60–7200 (600 por defecto, por paso), memory_max_mb y cpu_quota_pct sin tope por defecto, run_as (app_user o root), rollback_on_failed_health |
autodeploy | enabled: si un push despliega |
Variables y secretos
Una variable con nombre de secreto (*_TOKEN, *_PASSWORD, *_KEY…), con secret: true o con una URL que trae usuario y contraseña se guarda cifrada y en el documento queda solo la referencia. Al leer, env muestra solo {name, scope, has_value}, nunca un valor. Mandar una variable sin value conserva la guardada.
- Los valores se escriben en
run.envybuild.env(0600) recién en la fasepreparar. Un valor con salto de línea se rechaza. - Cambiar una variable de build (
NEXT_PUBLIC_*,VITE_*) obliga a compilar de nuevo: elreconfigurelo hace con el mismo código y sin release. - En el log de cada comando la carga secreta se reemplaza por su tamaño y la salida lleva cada secreto tapado (
[redacted]).
Pasos largos, topes y salud
install, build y release corren cada uno en su propia unidad transitoria, con el usuario de la app, /tmp privado y set -e (corta en el primer comando que falla):
sudo systemd-run --unit=ardu-deploy<pk>-<paso> --wait --collect --pipe \
-p User=ardu-app<pk> -p RuntimeMaxSec=<build_timeout_s> -p NoNewPrivileges=yes -p PrivateTmp=yes \
-p EnvironmentFile=/etc/ardumaker/deploy/ardu-repo<pk>/build.env \
/bin/bash /etc/ardumaker/deploy/ardu-repo<pk>/<paso>.sh| Salud | Qué se comprueba |
|---|---|
http | curl a 127.0.0.1:<puerto><ruta> hasta 25 veces; sin expect vale cualquier respuesta que no sea 5xx |
unit | Cada unidad activa dos veces con 8 s en el medio y sin reinicios nuevos |
timer | El .timer habilitado y activo, y su horario válido |
static | Existe la carpeta y el nginx del VPS responde sin 5xx para el dominio |
Puertos y publicación
La app escucha en un puerto interno del VPS. Si se instala por Apps sin port (o con "auto"), se toma el primer puerto libre. Los puertos de los motores y del agente nunca se publican. La dirección automática es git-<id> dentro de la zona de la plataforma; también se puede usar un dominio propio que apunte al VPS.
La fase publicar escribe el server block en el nginx del VPS; el DNS, el proxy hacia el VPS, el certificado y la comprobación del enlace por HTTPS los hace la publicación de Apps. Si esa parte falla (por ejemplo, el DNS todavía no propagó), el reintento publica sin volver a construir. Un nginx editado a mano no se pisa.
Push, commits puntuales y vuelta atrás
- Un push de otra rama se ignora. Con
autodeployapagado, una entrega repetida de GitHub o un deploy ya corriendo, el push anota los commits pero no despliega, y la respuesta dice por qué. redeployconcommit_shadeja la carpeta en ese commit de la rama y corre unupdatenormal, con release si está confirmada.rollbackconrun_idvuelve al commit de un deploy que terminó bien (git reset --hard <sha>) con la configuración de ahora. Se rechaza un deploy de otro repo, uno fallido o el que ya está desplegado.- Con
rollback_on_failed_health: true, un deploy disparado por push que falla ensaludvuelve una vez al último commit sano y apaga el autodeploy, dejando el motivo escrito.
Desvincular un repo
Borrar el repo quita lo que el documento creó: unidades y temporizadores, contenedores, la carpeta de la plataforma, el usuario de la app con su carpeta y la entrada de safe.directory; después la unidad y el contenedor principales, el clon y el webhook de GitHub. Lo adoptado no se toca.
Esquema
- GitHub: push a la rama seguida; webhook firmado
- Backend de ArduMaker: registra commits y encola el deploy
- Agente del VPS: ejecuta cada fase
- Clon + build:
systemd-runcomoardu-app<pk> - `ardu-repo<pk>.service`: la app en
127.0.0.1:<puerto> - nginx del VPS: server block del dominio
- Internet: HTTPS en el dominio publicado
Resultado esperado
El repo queda pulled (u online tras la instalación) con last_commit_sha igual al commit que está en la carpeta. ver_repo_github_vps trae last_runs: origen (webhook, manual o mcp), commits, resultado y cómo le fue a cada fase. Si se publicó, ver_app muestra la URL HTTPS solo cuando el enlace se verificó.
systemctl status ardu-repo<pk>.service
curl -I http://127.0.0.1:<puerto>/Errores y resolución
| Error | Causa | ¿Reintentar? |
|---|---|---|
github_no_vinculado | Quien pide no tiene GitHub conectado o el token venció | Sí, tras volver a conectar GitHub |
repo_inaccesible / rama_inexistente | La cuenta no lee el repo o la rama no existe | No |
clon_fallido / github_no_responde | Falló el clon (no deja nada) o GitHub no contestó | Sí |
puerto_ocupado | Otra app del VPS ya usa ese puerto | Con otro puerto |
deploy_en_curso | Ya había otro deploy corriendo en ese repo | Sí, cuando termine |
preparacion_fallida, pull_fallido, build_fallido, release_fallida, app_no_arranco, salud_fallida, nginx_del_vps_invalido | La fase que falló | A mano, nunca solo |
config_invalida | El documento no valida; errors por ruta (processes.web.port) | Corrigiendo el documento |
preparar failed - missing value for <VARIABLE>: falta el valor de una variable; el deploy se frena antes de tocar el VPS.- Aviso
limits_not_applied: el VPS rechazó los topes de memoria o CPU y el paso corrió sin ellos. - Aviso
health_check_failed: en un repo de configuración heredada la salud que no pasa es un aviso, no un error. - La app contesta solo en
[::1]: el nginx del VPS llega por127.0.0.1; hay que escuchar en127.0.0.1o0.0.0.0.
Dónde mirar: ver_repo_github_vps (últimos deploys y fases), listar_logs_repo_github_vps y ver_log_repo_github_vps (comando y salida, con el token y los secretos tapados), y ver_operacion_app para la operación de Apps.
Relación con MCP
El deploy de Git tiene acceso MCP completo; referencia en /mcp/docs y en /mcp/deploy. Las escrituras las puede hacer cualquier miembro con acceso al VPS, salvo un agente autorizado solo lectura.
| Tipo | Herramientas |
|---|---|
| Lectura | listar_repos_github_vps, ver_repo_github_vps, listar_commits_repo_github_vps, ver_commit_repo_github_vps, listar_logs_repo_github_vps, ver_log_repo_github_vps, ver_asistente_git_app, ver_config_deploy_app, ver_propietarios_github |
| Modifican | instalar_app, vincular_repo_github_vps, crear_repo_github_vps, editar_repo_github_vps, guardar_config_deploy_app, redeploy_repo_github, controlar_app, publicar_repo_en_dominio |
| Destructivas | eliminar_repo_github_vps, desinstalar_app |
controlar_app sobre una app git acepta redeploy (con commit_sha opcional), rollback (con run_id) y reconfigure (aplica el documento sin traer código). publicar_repo_en_dominio hace en una llamada dominio, verificación, activación con SSL, server block e instalación; con simular=true muestra el plan y con desde retoma donde falló.