Desarrollo y bases de datos

Configurar deploy automático desde GitHub a cPanel - Caso ejemplo

Revisado el · github · despliegues

Importante: Actualmente dispones de la herramienta Deploy un sistema avanzado y sencillo de usar que se integra a la perfección con tu repositorio de GitHub, más información en Deploy, herramienta de desplieque automático desde GitHub

En este caso ejemplo trataremos un escenario frecuente en el desarrollo web y de software, por un lado, el uso de un repositorio git remoto en el que mantenemos nuestro código, en este caso por uso extendido, GitHub, que utilizaremos como control de versiones, y desde el que automáticamente aplicaremos los cambios en nuestra web o app en producción en el servicio de hosting con cPanel, incluido cualquier acción que requiera (limpiar cache, sesiones, copiar ficheros...) e incluso en última instancia, haciendo que el deploy se ejecute en cada commit que realicemos sobre nuestro repositorio en GitHub.

Antes de empezar, ten en cuenta que partimos de la base en la que tienes el repositorio creado en GitHub y conocimientos básicos del uso de git y también del manejo básico de la terminal sería idóneo, de todas formas cada comando será explicado para que los entiendas y así evitar cualquier error derivado de su ejecución.

Preparando el terreno

Para nuestro ejemplo vamos a hacer una puesta en producción y estrategía de deploy de una aplicación desarrollada en Laravel, de esa forma cubriremos varios puntos avanzados a tener en cuenta, pero el proceso es similar para otras webs o frameworks, y las herramientas que proveemos se adaptan a cada situación.

Habilitando el acceso SSH

Al ser una opción avanzada no requerida de forma común, por defecto el acceso SSH está desactivado, lo puedes activar de forma sencilla desde el área de clientes.

Creando una clave segura para SSH

La clave debe servir únicamente para este repositorio. No reutilices la clave personal de la cuenta ni una clave con acceso a todos tus repositorios.

Accede vía SSH a tu cuenta y crea un par dedicado:

umask 077
ssh-keygen -t ed25519 -f ~/.ssh/github_laravel_deploy -C "deploy laravel cpanel"
chmod 600 ~/.ssh/github_laravel_deploy
chmod 644 ~/.ssh/github_laravel_deploy.pub

Usa una passphrase y un agente SSH cuando el proceso permita interacción. Si la automatización no admite agente, protege especialmente la clave sin passphrase: permisos 600, fuera de public_html, sin compartirla y con acceso de solo lectura al repositorio.

Configurando nuestra SSH key en GitHub

Accede al repositorio concreto en GitHub, sección Settings, Deploy keys, y añade el contenido de ~/.ssh/github_laravel_deploy.pub. No actives permisos de escritura si el servidor solo necesita clonar y desplegar.

Configura ~/.ssh/config para que Git use exclusivamente esa identidad con GitHub. Mantén el fichero con permisos 600:

Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_laravel_deploy
    IdentitiesOnly yes

Antes de aceptar por primera vez el host, obtén las huellas oficiales de GitHub desde su documentación por una vía independiente y compáralas con las presentadas por la conexión. No respondas yes si no coinciden. Tras verificar la huella, prueba el acceso:

chmod 600 ~/.ssh/config
ssh -T git@github.com

Si todo fue bien, recibirás una respuesta afirmativa por parte del servidor de GitHub.

Hi user! You've successfully authenticated, but GitHub does not provide shell access.

Si recibes un error, comprueba que la clave pública del repositorio coincide con la del hosting y que la clave solo tiene acceso al repositorio previsto. Revócala en GitHub si se expone o deja de utilizarse.

Definiendo las tareas durante la aplicación de cambios o deploy

Toda puesta en producción suele requerir ejecutar comandos y acciones, puede ser el actualizar nuestras dependencias, mover o copiar ficheros, ejecutar tareas del propio framework, entre otras.

Estas tareas se pueden automatizar por medio del fichero .cpanel.yml, es un fichero en formato YAML con el que podemos definir cualquier acción necesaria y que será procesado al hacer un deploy desde la interfaz de gestión de repositorios de cPanel o desde la terminal por medio de la API.

Vamos a realizar este paso antes de clonar el repositorio en cPanel ya que necesitamos incluir este fichero en nuestro repositorio en GitHub para que esté disponible en cada aplicación de cambios que llevemos a cabo.

Para ello crearemos un fichero llamado .cpanel.yml en el directorio raíz de nuestro repositorio, su contenido sería:

---
deployment:
  tasks:
    - chmod 755 ~/repositories
    - chmod 755 ~/repositories/laravel
    - chmod 755 ~/repositories/laravel/public
    - /opt/cpanel/composer/bin/composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
    - /usr/local/bin/php artisan config:cache
    - /usr/local/bin/php artisan view:cache
    - /usr/local/bin/php artisan route:cache

Su formato es bastante sencillo, un comando por línea definido dentro de las tareas a ejecutar. Usamos composer install, no composer update, para instalar exactamente las versiones bloqueadas en composer.lock; el lock debe revisarse y actualizarse fuera de producción.

Las migraciones no se ejecutan automáticamente en este ejemplo. Pruébalas primero en staging sobre una copia reciente de producción, revisa que sean migraciones hacia delante y prepara su reversión. Justo antes de ejecutarlas en producción, detén las escrituras y crea un snapshot de la base de datos cuya restauración hayas verificado. Un rollback del código no revierte el esquema ni recupera datos eliminados.

En el comando relacionado con composer y con php hemos indicado la ruta absoluta a los binarios para que la tarea los localice sin problemas, si no sabes cuál es la ruta absoluta a algún programa basta con ejecutar which seguido del alias y nos devolverá dicha ruta:

which php
-> /usr/local/bin/php

Clonando el respositorio desde GitHub

Aunque podríamos clonar directamente el repositorio vía SSH (recuerda que tienes control absoluto sobre git, no habría limitaciones para hacer uso de este comando como harías en cualquier otra máquina), lo vamos a configurar usando la interfaz de gestión de Git de cPanel para beneficiarnos de los mecanismos de deploy que veremos más adelante.

Copia la URL de clonado desde tu repostorio en GitHub, seleccionando el tipo SSH.

Nos dirigimos a cPanel, sección Git™ Version Control, y hacemos click en Create para crear un nuevo repositorio.

En Clone URL, introducimos la dirección de nuestro repositorio previamente clonada desde GitHub, Repository Path será la ruta donde alojaremos este repositorio, en nuestro caso hemos elegido repositories/laravel, de esa forma tendremos una mejor organización si hay varios dominios en el servidor, Repository Name sería un nombre identificativo del repositorio.

Click en Create y esperamos que se complete el clonado.

Si navegamos a la ruta repositories/laravel, ya sea vía FTP, administrador de ficheros de cPanel, o mejor aún, vía SSH, veremos que nuestro repositorio de GitHub ya se encuentra clonado correctamente.

Preparando el acceso web a Laravel

Como puedes ver tenemos nuestro repositorio en un subdirectorio de la raíz de la cuenta, en el caso del framework Laravel, necesitamos un paso adicional, que el acceso al dominio se realice sobre el directorio public del propio framework, lo que significa que el servidor web debe apuntar a:

/home/user/repositories/laravel/public

Si fuese un dominio adicional no tendría mayor complicación, desde cPanel podemos modificar la ruta inicial del dominio para que sea el subdirectorio que queramos, pero en este caso ejemplo, estamos trabajando sobre el dominio principal de la cuenta, por lo que procedemos de otra forma.

Antes de cambiar public_html, ejecuta Update from Remote y Deploy HEAD commit mientras la web anterior sigue activa. Revisa el log y confirma que el destino contiene index.php, vendor/autoload.php y las cachés esperadas. Crea además una copia actual de ficheros y base de datos. No borres el directorio existente: renómbralo para conservar una reversión inmediata.

TARGET="$HOME/repositories/laravel/public"
STAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP="$HOME/public_html.before-laravel-$STAMP"

test -d "$TARGET" && test -f "$TARGET/index.php" || exit 1
test -f "$HOME/repositories/laravel/vendor/autoload.php" || exit 1
test -d "$HOME/public_html" && test ! -L "$HOME/public_html" || exit 1
mv "$HOME/public_html" "$BACKUP"
ln -s "$TARGET" "$HOME/public_html"
test "$(readlink -f "$HOME/public_html")" = "$(readlink -f "$TARGET")" || exit 1
printf 'Ruta de reversión: %s\n' "$BACKUP"

Ejecutamos un listado para verificar el enlace y probamos el dominio antes de eliminar ninguna copia:

ls -l ~
curl --fail --silent --show-error https://tudominio.example/ --output /dev/null

Y obtendremos una salida donde veremos que efectivamente, está correctamente creado:

Todos estos pasos podríamos incluirlos en el propio fichero .cpanel.yml, al ser una acción a realizar una única vez, hemos preferido hacerlo manualmente y también para que entiendas mejor la configuración que se realiza.

Si la validación falla, revierte en la misma sesión sin borrar el backup. Si abriste otra sesión, asigna primero a BACKUP la ruta exacta mostrada por el comando anterior:

test -L "$HOME/public_html" || exit 1
rm "$HOME/public_html"
mv "$BACKUP" "$HOME/public_html"
test -d "$HOME/public_html" || exit 1

Conserva el directorio renombrado hasta terminar el periodo de observación.

Las dependencias se han instalado durante el deploy previo mediante composer. No cambies el enlace si ese deploy o sus validaciones fallan.

Para los despliegues siguientes, accede a Git™ Version Control, entra en Manage sobre el repositorio y abre Pull or deploy. Ejecuta Update from Remote y, cuando finalice, Deploy HEAD commit.

Si todo fue bien, recibiremos confirmación en la zona lateral derecha. Revisa también el log y valida el dominio; si falla, ejecuta la reversión anterior.

Accedemos de nuevo a nuestro dominio y ahora si, vemos que nuestra app en Laravel ya está funcionando.

Definiendo el flujo de trabajo

Ya tendrías todo el proceso configurado, desde este momento, podrás trabajar sobre tu repositorio en GitHub y cuando estés decidido a aplicar los cambios y ponerlos en producción, simplemente tendrías que ir a cPanel, hacer un Update from Remote para tomar los cambios y a continuación Deploy HEAD Commit para ejecutar las tareas.

Estas mismas acciones las podemos realizar desde nuestra terminal, primero haciendo un git pull para tomar los cambios desde GitHub y después usando la API de cPanel para ejecutar el deploy.

# Navegamos al repositorio
cd ~/repositories/laravel

# Verificamos la rama y tomamos solo un avance rápido desde GitHub
test "$(git branch --show-current)" = "main" || exit 1
git pull --ff-only origin main

# Usamos uapi para ejecutar las tareas del fichero .cpanel.yml
uapi VersionControlDeployment create repository_root='/home/user/repositories/laravel'

Desgranando el último comando, uapi es la interfaz de acceso a la API de cPanel vía local, VersionControlDeployment create le está diciendo que queremos ejecutar el deploy al igual que haríamos usando la interfaz gráfica de cPanel, y finalmente repository_root debe ser la ruta de nuestro repositorio. En una automatización, encierra toda esta secuencia en el lock que se muestra más adelante para impedir ejecuciones simultáneas.

Este comando nos devolverá una salida similar a la siguiente:

apiversion: 3
func: create
module: VersionControlDeployment
result:
  data:
    deploy_id: 6
    log_path: /home/user/.cpanel/logs/vc_1644663792.81236_git_deploy.log
    repository_root: /home/user/repositories/laravel
    sse_url: /sse/UserTasks/00000000_620793f0c68e52/vc_1644663792.81236_git_deploy.log
    task_id: 00000000/620793f0c68e52
    timestamps:
      queued: '1644663792.82822'
  errors: ~
  messages: ~
  metadata: {}

  status: 1
  warnings: ~

Lo más importante es "status" donde 1 significa que se ejecutó correctamente, y log_path, que sería el registro del deploy que podemos abrir para comprobar si las tareas del .cpanel.yml se ejecutaron sin errores.

Si vamos a hacer un deploy de nuestra web de forma frecuente, quizás nos interese crear una función con un nombre acortado que ejecute estas dos acciones en una única vez.

Vamos a editar el fichero .bashrc que tienes en la raíz de la cuenta, y justo al final añadimos:

# User specific aliases and functions

deploy() {
    (
        flock -n 9 || exit 1
        cd "$HOME/repositories/laravel" &&
        test "$(git branch --show-current)" = "main" &&
        git pull --ff-only origin main &&
        uapi VersionControlDeployment create repository_root="$HOME/repositories/laravel"
    ) 9>"$HOME/.deploy-laravel.lock"
}

La función fija la rama main, exige un avance rápido y usa un lock para impedir dos despliegues simultáneos. Adapta la rama y rutas una sola vez, y pruébala primero en staging.

Recuerda salir de la sesión abierta y volver a entrar para que la función comience a estar disponible, también puedes ejecutar source ~/.bashrc para que se apliquen los cambios en la sesión abierta.

Ahora si ejecutamos el comando deploy que hemos creado, en un solo comando haremos todos los pasos para poner en producción los cambios desde nuestro repositorio y ejecutar las tareas necesarias.

Deploy automático en cada commit sobre nuestro repositorio en GitHub

Si solo aplicas cambios finales desde tu repositorio local al que tienes en GitHub, puedes considerar que cada commit sobre GitHub debería ser puesto en producción, y por lo tanto buscarías automatizar el paso anterior.

Usa preferentemente la herramienta Deploy indicada al principio. No publiques una URL que ejecute comandos al visitarla: permitiría desplegar sin autenticación y desde una petición GET.

Si implementas un receptor propio, debe cumplir todos estos requisitos antes de ejecutar nada:

  1. Aceptar solo POST sobre HTTPS y limitar el tamaño del cuerpo.
  2. Guardar un secreto aleatorio fuera del directorio público y verificar X-Hub-Signature-256 sobre el cuerpo bruto con HMAC SHA-256 y comparación en tiempo constante.
  3. Rechazar cualquier evento distinto de push, cualquier repository.full_name que no sea el repositorio exacto y cualquier ref que no sea la rama permitida, por ejemplo refs/heads/main.
  4. Adquirir un lock exclusivo antes de actualizar; si ya hay un despliegue, responder sin lanzar otro.
  5. Ejecutar comandos y rutas fijos, sin interpolar valores del JSON en la shell, y usar git pull --ff-only sobre la rama comprobada.
  6. Registrar entrega, commit y resultado sin guardar el secreto, y devolver error si falla una validación.

La restricción por IP puede añadirse como defensa adicional, pero no sustituye la firma. Crea primero el webhook contra staging, fuerza firmas inválidas, repositorios y ramas incorrectos, despliegues simultáneos y un rollback completo antes de conectarlo a producción.

A continuación accedemos a nuestro repositorio en GitHub, opción Settings, Webhooks y configuramos la URL HTTPS y el mismo secreto del receptor.

Desde ese momento, GitHub notificará al receptor, pero solo un push firmado del repositorio y rama autorizados podrá poner el despliegue en cola.

Depurar errores de deploy

Puede suceder que algún comando no esté funcionando, o tengamos un error que no preveíamos, para ello podemos ver los registros de cada deploy en el directorio .cpanel/logs, los registros siguen el formato "NUMERO_git_deploy.log".

De esa forma podrás revisar la ejecución de cada comando desglosado paso a paso:

164466267.976022855
$ chmod 755 ~/repositories

Task completed with exit code 0.

1644662676.980349011
$ chmod 755 ~/repositories/laravel

Task completed with exit code 0.

1644662676.984084793
$ chmod 755 public

Task completed with exit code 0.

1644662676.987835992
$ /opt/cpanel/composer/bin/composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
Installing dependencies from lock file
Nothing to install, update or remove

$ /usr/local/bin/php artisan config:cache
Configuration cache cleared!
Configuration cached successfully!

Task completed with exit code 0.

1644663799.347817739
$ /usr/local/bin/php artisan view:cache
Compiled views cleared!
Blade templates cached successfully!

Task completed with exit code 0.

1644663799.534180019
$ /usr/local/bin/php artisan route:cache
Route cache cleared!
Routes cached successfully!

Task completed with exit code 0.

1644663799.71281
Build completed with exit code 0

La flexibilidad del entorno es la clave, tu decides como trabajar

Hemos partido de un escenario ejemplo que nosotros mismos usamos en nuestro día a día, pero la realidad es que la flexibilidad de las herramientas que te hemos mostrado te permiten configurar tu flujo de trabajo para que se adapte a las necesidades tanto si eres un único desarrollador, como si sois un equipo.

Por ejemplo, en vez de tener un repositorio en producción, podrías hacer que el repositorio solo actúe de vía de entrada para los cambios, y que el proceso de deploy copie dichos cambios a otra ruta dentro de la cuenta usando los comandos rsync (sincronizar) o cp (copiar), también podrías prescindir de GitHub y trabajar directamente aplicando cambios sobre tu repositorio creado en cPanel.

Al final del día se trata de desarrollar y trabajar de la forma que resulte más ágil en cada caso y proyecto específico, de ahí que la flexibilidad del entorno y las herramientas, además del conocimiento acerca de estas marque un punto de inflexión en los procesos de los desarrolladores y administradores.

También te puede ayudar

  1. Clonado y deploy desde repositorio remoto privado
  2. Deploy automático desde GitHub con rollback instantáneo
  3. Automatización de tareas de deploy en Git con el fichero .cpanel.yml
  4. Despliegue automático con Deployer.org para cualquier app