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, comouser@hostohost.ssh-key- Obligatorio. Clave privada con acceso al servidor.ssh-port- Puerto, cuando el servidor no escucha en el 22.known-hosts- Contenido paraknown_hosts. Sin él, la clave del host se obtiene en la primera conexión.target- Qué copia de la aplicación desplegar. Por defectoproduction, opreview:<rama>en un pull request.environmentes el nombre obsoleto, aún respetado cuandotargetno 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 hostvelastack.app.action-deploy,destroyoauto.autodespliega en todos los eventos salvo en un pull request cerrado, donde elimina la previsualización — y únicamente una previsualización: un eventoclosedcuyo objetivo sea cualquier otra cosa hace fallar el job. Por defectoauto.confirm-name- El nombre de la aplicación, obligatorio cuandoaction: destroyapunta a producción o a un entorno con nombre comostaging. Nunca hace falta para las previsualizaciones.comment- En pull requests, mantener un comentario actualizado con la URL de la previsualización. Por defectotrue.github-token- Token con el que se publica el comentario. Por defecto el token del workflow, que necesitapull-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. Ponfalsepara 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.nvmrco.node-version, y si no, 24.install- Ejecutarnpm ciprimero. Por defectotrue.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ónhostnames- Todos los nombres de host en los que se sirve el objetivo, separados por comastarget- El objetivo que se desplegó o eliminó, comoproductionopreview: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.jsonestá versionado — contiene el id de la aplicación con el que el servidor indexa todo- El repositorio usa npm (existe un
package-lock.json)