GitHub deploy

Clones, builds and publishes a GitHub repo on the VPS; redeploys on every push, phase history and rollback.

  • Category: Servers and infrastructure
  • Status: Available
  • Requirements: An active company VPS with the deploy_github feature available; The linking person's GitHub account connected to ArduMaker with access to the repo; Being a company member with access to that VPS (in Git a member can do the same as an admin)
  • MCP tools (Read only): 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
  • MCP tools (Write or modify): instalar_app, vincular_repo_github_vps, crear_repo_github_vps, editar_repo_github_vps, redeploy_repo_github, guardar_config_deploy_app, controlar_app
  • MCP tools (Destructive): eliminar_repo_github_vps, desinstalar_app, publicar_repo_en_dominio

Markdown

What can you do with it?

Git deploy takes a GitHub repository to a VPS and keeps it up to date: it clones it, builds it, leaves it running as a systemd service (or container), publishes it with HTTPS and redeploys it on every push to the chosen branch.

  • Detect what the repo is (Node, Django, Python, static site, Docker, Procfile) and propose build and start commands.
  • Declare in a deploy document the phases (pre_pull, install, build, release), the processes (services, timers, one-off jobs, static, Docker, TCP), environment variables and publishing.
  • Deploy automatically on every push, by hand, or a specific commit of the branch.
  • Roll back to the commit of a previous deploy that finished well.
  • See the deploy history with every phase, its timing and the commands with their output (secrets redacted).
  • Create a new GitHub repository from ArduMaker and deploy it.

When should you use it?

  • A website, an API or a worker that lives on GitHub and must always match the main branch.
  • Scheduled jobs of the project (a timer) that must travel with the code.
  • A team that wants to see which commit is in production and be able to roll back without logging into the server.

How does it work in ArduMaker?

1. Link and clone

There are two doors. The recommended one is installing it as an app (instalar_app with app_type: git, repo_id, branch and optionally port and the deploy document): it lands in the Apps inventory and the operation is durable. The other is the VPS GitHub tab (vincular_repo_github_vps), which only clones and runs in the background.

When linking, ArduMaker creates a webhook on GitHub with a secret of its own for that repo: each delivery arrives signed with HMAC-SHA256 and nothing secret in the URL. The repo is cloned into <deploy-folder>. The URL with the GitHub token stays in the .git folder, owned by root and unreadable by the app.

2. Detect and configure

ver_asistente_git_app lists the repos the GitHub account can read, their branches and, with repo and branch, what it detects by reading files without running anything: framework, build and start commands and a free VPS port. Known formats are Docker (also Compose), Procfile, Node web, Django, Vite static site, Python web and manual. A proposed migration always arrives unconfirmed: the document does not validate until a person confirms it.

What every deploy runs is always a document: the one saved with guardar_config_deploy_app or, if the repo never saved one, the one derived from its usual scripts (pre-pull, post-pull and services). Saving a document migrates the repo: from then on the document rules.

3. Deploy in phases

Every deploy runs these phases in order and records them in the history. If one fails, the following ones do not run and the repo stays in error with a short text (Deploy failed at the <phase> phase), never the raw output of a command.

PhaseWhat it does
prepararApp Linux user, platform folder, variable files, Docker if needed
pre_pullphases.pre_pull in the clone folder
pullPulls the branch (or leaves the folder at the requested commit)
installphases.install, long step
buildphases.build, long step; then the containers of docker processes
releaseThe migration, only if confirmed
procesosWrites or restarts units, timers and one-off jobs
publicarDomain server block in the VPS nginx, if the profile publishes
saludChecks that the app answers or that the units stay alive

With the app installed in Apps, the deploy is run by the operations runner and resumes if the worker dies. A repo without an app runs the same deploy in the background. Several pushes in a row are merged: at most one deploy running and one queued per app.

What actually changes?

<pk> is the repo number in ArduMaker. With policies.run_as: "root" the user is not created and everything belongs to root.

WhatWhereOwner and mode
App userardu-app<pk>, system user, no login—
Clone<deploy-folder>the user's; .git owned by root
Platform folder/etc/ardumaker/deploy/ardu-repo<pk>/root:ardu-app<pk> 0750
Runtime and build variables…/run.env, …/build.envardu-app<pk> 0600
Scripts of each step and their output…/install.sh, build.sh, release.sh, <paso>.log0750 / root
Main service/etc/systemd/system/ardu-repo<pk>.serviceroot 0644
Other processes and timersardu-repo<pk>-<proceso>.service / .timerroot 0644
Containersardu-repo<pk>, ardu-repo<pk>-<proceso>—
Server block/etc/nginx/sites-available/<dominio>.conf—

A unit that did not change is only restarted; one that changed is rewritten. An adopted unit (from an older repo) is restarted and never written or deleted. Whatever a deploy created and the document no longer has is deleted on the next deploy.

The deploy document

It is a versioned JSON (schema_version: 2). It is read with ver_config_deploy_app and fully replaced with guardar_config_deploy_app (apply=true applies it right away with a reconfigure).

PartValues
profilestatic, web, worker, scheduled, job, docker, network and code (code only: each push fetches the repository and nothing else; no install, build, migration, processes or publishing)
phasespre_pull, install, build and release (command, confirmed, on_update)
processesUp to 20, of type service, timer (with schedule), oneoff, static, docker or tcp; ports 1024 to 65535, no repeats
network.publish.modenone, auto_subdomain, domain, port
network.health.typehttp, unit, timer, static
envList of {name, value} with scope build, run or both
policiesbuild_timeout_s 60–7200 (600 by default, per step), memory_max_mb and cpu_quota_pct unlimited by default, run_as (app_user or root), rollback_on_failed_health
autodeployenabled: whether a push deploys

Variables and secrets

A variable with a secret-like name (*_TOKEN, *_PASSWORD, *_KEY…), with secret: true or with a URL carrying user and password is stored encrypted and only a reference stays in the document. On read, env shows only {name, scope, has_value}, never a value. Sending a variable without value keeps the stored one.

  • Values are written to run.env and build.env (0600) only in the preparar phase. A value with a line break is rejected.
  • Changing a build variable (NEXT_PUBLIC_*, VITE_*) requires a rebuild: reconfigure does it with the same code and without release.
  • In each command's log the secret payload is replaced by its size and the output has every secret redacted ([redacted]).

Long steps, limits and health

install, build and release each run in their own transient unit, as the app user, with a private /tmp and set -e (stops at the first failing command):

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
HealthWhat is checked
httpcurl to 127.0.0.1:<port><path> up to 25 times; without expect any answer other than 5xx is fine
unitEach unit active twice with 8 s in between and no new restarts
timerThe .timer enabled and active, and its schedule valid
staticThe folder exists and the VPS nginx answers without 5xx for the domain

Ports and publishing

The app listens on an internal VPS port. If installed through Apps without port (or with "auto"), the first free port is taken. Engine and agent ports are never published. The automatic address is git-<id> inside the platform zone; a custom domain pointing to the VPS can be used too.

The publicar phase writes the server block in the VPS nginx; DNS, the proxy to the VPS, the certificate and the HTTPS link check are done by Apps publishing. If that part fails (for example, DNS has not propagated yet), the retry publishes without rebuilding. A hand-edited nginx is not overwritten.

Pushes, specific commits and rollback

  • A push to another branch is ignored. With autodeploy off, a repeated GitHub delivery or a deploy already running, the push records the commits but does not deploy, and the answer says why.
  • redeploy with commit_sha leaves the folder at that commit of the branch and runs a normal update, with release if confirmed.
  • rollback with run_id returns to the commit of a deploy that finished well (git reset --hard <sha>) with the current configuration. A deploy of another repo, a failed one or the one already deployed is refused.
  • With rollback_on_failed_health: true, a push-triggered deploy that fails salud goes back once to the last healthy commit and turns autodeploy off, recording the reason.

Unlinking a repo

Deleting the repo removes what the document created: units and timers, containers, the platform folder, the app user with its folder and the safe.directory entry; then the main unit and container, the clone and the GitHub webhook. Adopted items are left alone.

Diagram

  1. GitHub: push to the tracked branch; signed webhook
  2. ArduMaker backend: records commits and queues the deploy
  3. VPS agent: runs each phase
  4. Clone + build: systemd-run as ardu-app<pk>
  5. `ardu-repo<pk>.service`: the app on 127.0.0.1:<port>
  6. VPS nginx: domain server block
  7. Internet: HTTPS on the published domain
From a push to the published app — The certificate is set up by the platform in front of the VPS; the VPS nginx routes the domain to the app's internal port.

Expected result

The repo ends up pulled (or online after installation) with last_commit_sha equal to the commit in the folder. ver_repo_github_vps returns last_runs: origin (webhook, manual or mcp), commits, result and how each phase went. If it was published, ver_app shows the HTTPS URL only once the link was verified.

systemctl status ardu-repo<pk>.service
curl -I http://127.0.0.1:<puerto>/

Errors and troubleshooting

ErrorCauseRetry?
github_no_vinculadoThe requester has no GitHub connected or the token expiredYes, after reconnecting GitHub
repo_inaccesible / rama_inexistenteThe account cannot read the repo or the branch does not existNo
clon_fallido / github_no_respondeThe clone failed (leaves nothing behind) or GitHub did not answerYes
puerto_ocupadoAnother app on the VPS already uses that portWith another port
deploy_en_cursoAnother deploy was already running on that repoYes, when it finishes
preparacion_fallida, pull_fallido, build_fallido, release_fallida, app_no_arranco, salud_fallida, nginx_del_vps_invalidoThe phase that failedBy hand, never by itself
config_invalidaThe document does not validate; errors by path (processes.web.port)After fixing the document
  • preparar failed - missing value for <VARIABLE>: a variable value is missing; the deploy stops before touching the VPS.
  • limits_not_applied warning: the VPS rejected the memory or CPU limits and the step ran without them.
  • health_check_failed warning: on a repo with inherited configuration a failing health check is a warning, not an error.
  • The app answers only on [::1]: the VPS nginx reaches 127.0.0.1; it must listen on 127.0.0.1 or 0.0.0.0.

Where to look: ver_repo_github_vps (latest deploys and phases), listar_logs_repo_github_vps and ver_log_repo_github_vps (command and output, with token and secrets redacted), and ver_operacion_app for the Apps operation.

MCP access

Git deploy has full MCP access; reference at /mcp/docs and /mcp/deploy. Writes can be done by any member with access to the VPS, except an agent authorized as read-only.

KindTools
Readlistar_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
Modifyinstalar_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
Destructiveeliminar_repo_github_vps, desinstalar_app

controlar_app on a git app accepts redeploy (with optional commit_sha), rollback (with run_id) and reconfigure (applies the document without pulling code). publicar_repo_en_dominio does domain, verification, SSL activation, server block and installation in one call; with simular=true it shows the plan and with desde it resumes where it failed.

Related features