velastack/action

velastack/action despliega una aplicación de Vela en tu propio servidor desde GitHub Actions. Compila en el runner y entrega la release a vela deploy por SSH, de modo que un despliegue desde CI y uno desde tu portátil hacen lo mismo.

Antes de empezar

Aprovisiona el servidor y despliega una vez desde tu propia máquina — ese primer despliegue es el que genera el id de la aplicación:

$ vela provision root@your-server
$ vela deploy --server root@your-server --domain example.com
$ git add .vela/project.json && git commit -m "Add the vela project id"

La acción se niega a ejecutarse hasta que .vela/project.json esté versionado. Un runner que no encuentra ningún id genera uno nuevo, y como su copia del repositorio se descarta al final del job, cada despliegue se instalaría como una aplicación nueva y dejaría huérfana la anterior en el servidor.

Después, dale a la acción una forma de entrar:

$ ssh-keygen -t ed25519 -f vela-deploy -C "github actions"
$ ssh-copy-id -i vela-deploy.pub root@your-server

Añade la clave privada al repositorio como un secreto llamado SSH_PRIVATE_KEY.

Uso

name: Deploy

on:
  push:
    branches: [main]

concurrency:
  group: deploy-prod
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: velastack/action@v1
        with:
          server: root@your-server
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
          domain: example.com

La acción instala las dependencias, compila, sube una release, ejecuta las migraciones, reinicia los servicios y comprueba la salud del resultado. Un despliegue que falla su comprobación de salud restaura la release anterior, migraciones incluidas, y hace fallar el job. Usa un grupo de concurrency para que dos pushes se pongan en cola en lugar de competir; el servidor también serializa los despliegues de un mismo objetivo, y descarta uno que resulte ser más antiguo que lo que se puso en línea entretanto — consulta Dos despliegues a la vez.

Previsualizaciones de pull requests

Añade los eventos de pull request y una clave de API, y cada pull request recibe su propia previsualización: una copia completa de la aplicación en el servidor, en <proyecto>--<rama>.velastack.app.

name: Deploy

on:
  push:
    branches: [main]
  pull_request:
    types: [opened, synchronize, reopened, closed]

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: deploy-${{ github.head_ref || github.ref_name }}
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: velastack/action@v1
        with:
          server: root@your-server
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
          api-key: ${{ secrets.VELA_API_KEY }}
          domain: example.com

El objetivo sale del evento cuando target no está definido: un push despliega production, un pull request despliega preview:<rama>. Cerrar el pull request, fusionado o no, elimina la previsualización del servidor — base de datos y archivos subidos incluidos, con una instantánea que se conserva dos semanas en la papelera del servidor — y retira su nombre de host. Se mantiene un comentario en el pull request actualizado con la URL de la previsualización en cada push, y para eso sirve pull-requests: write; el token de un fork no puede comentar, y eso nunca hace fallar el despliegue.

La eliminación va montada en el evento closed, así que mantenlo en el disparador. Si el workflow ejecuta pruebas antes de desplegar, deja que ese evento se las salte: una rama se cierra a menudo precisamente porque está en rojo, y condicionar la limpieza a que esté en verde deja la previsualización atrás para siempre.

jobs:
  check:
    if: github.event.action != 'closed'
    # ...
  deploy:
    needs: check
    if: ${{ !cancelled() && (github.event.action == 'closed' || needs.check.result == 'success') }}
    # ...

Las previsualizaciones nunca heredan domain — ese es de producción. Para servirlas también en tu propio dominio, añade una base de previsualización en la página de Dominios del proyecto.

VELA_API_KEY es lo que registra los despliegues en velastack.dev y da a las previsualizaciones su nombre de host. Crea una en velastack.dev/api-keys/new y añádela como secreto del repositorio. El proyecto también tiene que estar vinculado, lo que vela link escribe en el mismo .vela/project.json que ya versionas. Sin la clave el despliegue sigue ejecutándose; simplemente no se registra, y una previsualización no tiene dónde servirse.

Si tu objetivo de producción tiene un nombre distinto de production, pásalo en todos los eventos salvo en un pull request, y déjalo vacío ahí para que el objetivo de previsualización pueda tomar el relevo. El valor vacío tiene que ir al final: '' es falso en las expresiones de GitHub, así que cond && '' || 'prod' parece un ternario pero devuelve prod en todos los eventos — y en un pull request cerrado eso intentaría eliminar producción. La acción se niega a eliminar cualquier cosa que no sea una previsualización en modo auto, pero los despliegues habrían aterrizado igualmente en producción.

          target: ${{ !startsWith(github.event_name, 'pull_request') && 'prod' || '' }}

Dependabot y forks

Los pull requests que abre Dependabot, y cualquier pull request desde un fork, se ejecutan sin los secretos del repositorio, así que la acción no tiene clave SSH y no puede llegar al servidor. Sáltate el job para ellos en lugar de dejar que falle; sin previsualización desplegada tampoco hay nada que eliminar al cerrar, de modo que la misma condición cubre el evento closed:

  deploy:
    if: github.event_name != 'pull_request' || (github.event.pull_request.user.login != 'dependabot[bot]' && !github.event.pull_request.head.repo.fork)

Entradas

  • server - Obligatorio. Destino SSH, como user@host o host.
  • ssh-key - Obligatorio. Clave privada con acceso al servidor.
  • ssh-port - Puerto, cuando el servidor no escucha en el 22.
  • known-hosts - Contenido para known_hosts. Sin él, la clave del host se obtiene en la primera conexión.
  • target - Qué copia de la aplicación desplegar. Por defecto production, o preview:<rama> en un pull request. environment es el nombre obsoleto, aún respetado cuando target no está definido.
  • domain - Nombre(s) de host en los que servir, separados por comas. Solo se necesita la primera vez, o cuando cambia. Nunca se aplica a las previsualizaciones.
  • api-key - Clave de API de velastack.dev, para que el despliegue se registre en el proyecto vinculado y las previsualizaciones reciban un nombre de host velastack.app.
  • action - deploy, destroy o auto. auto despliega en todos los eventos salvo en un pull request cerrado, donde elimina la previsualización — y únicamente una previsualización: un evento closed cuyo objetivo sea cualquier otra cosa hace fallar el job. Por defecto auto.
  • confirm-name - El nombre de la aplicación, obligatorio cuando action: destroy apunta a producción o a un entorno con nombre como staging. Nunca hace falta para las previsualizaciones.
  • comment - En pull requests, mantener un comentario actualizado con la URL de la previsualización. Por defecto true.
  • github-token - Token con el que se publica el comentario. Por defecto el token del workflow, que necesita pull-requests: write.
  • project - Sobrescribir el nombre del proyecto.
  • health-path - Ruta que solicita la comprobación de salud.
  • remote-db - Compilar contra la base de datos del servidor. Sin definir, decide la CLI: activado una vez que el objetivo ha sido desplegado. Pon false para compilar contra una desechable.
  • working-directory - Directorio que contiene la aplicación. Por defecto ..
  • node-version - Versión de Node.js con la que compilar. Por defecto la de .nvmrc o .node-version, y si no, 24.
  • install - Ejecutar npm ci primero. Por defecto true.
  • vela-version - Versión de la CLI a ejecutar. Por defecto la que fija el proyecto.

Salidas

  • release - Identificador de la release que se activó
  • url - URL en la que se sirve la aplicación
  • hostnames - Todos los nombres de host en los que se sirve el objetivo, separados por comas
  • target - El objetivo que se desplegó o eliminó, como production o preview:my-branch

En una eliminación, release, url y hostnames quedan vacíos.

Páginas que se prerenderizan a partir de datos

Una página prerenderizada se renderiza una vez, en tiempo de compilación, contra la base de datos que la compilación pueda ver. En un runner esa sería una base de datos vacía y desechable, así que esas páginas saldrían llenas de valores por defecto.

El despliegue lo evita por sí solo: una vez que el objetivo ha sido desplegado, la compilación se renderiza contra la base de datos de esa instancia, tunelizada por la misma conexión SSH. Las credenciales del superusuario se obtienen del servidor, así que no entran secretos nuevos en CI, y la compilación solo lee. No hay que configurar nada para esto.

Pon remote-db: false para renunciar y compilar contra una base de datos desechable:

      - uses: velastack/action@v1
        with:
          server: root@your-server
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
          remote-db: false

Pasa domain en el primer despliegue desde CI, para que las páginas prerenderizadas vean el dominio como url.origin y no el host de relleno de SvelteKit. Sus enlaces canónicos, como el nombre de la aplicación, salen de src/lib/site.ts, no de la base de datos.

Secretos

Las variables de entorno de producción viven en tu servidor, no en la acción ni en la release. Defínelas una vez con vela env. Todo lo que necesite la compilación — a diferencia de la aplicación en ejecución — va en el workflow, porque tiene que existir en el runner:

      - uses: velastack/action@v1
        env:
          POCKETBASE_SUPERUSER_EMAIL: ${{ secrets.POCKETBASE_SUPERUSER_EMAIL }}
          POCKETBASE_SUPERUSER_PASSWORD: ${{ secrets.POCKETBASE_SUPERUSER_PASSWORD }}
        with:
          server: root@your-server
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}

Fijar la clave del host

Por defecto, la acción confía en la clave del host del servidor la primera vez que se conecta. Para fijarla en su lugar, captúrala una vez y pasa la salida como entrada known-hosts:

$ ssh-keyscan -H your-server

Requisitos

  • La aplicación se compila con @sveltejs/adapter-node
  • .vela/project.json está versionado — contiene el id de la aplicación con el que el servidor indexa todo
  • El repositorio usa npm (existe un package-lock.json)