Skip to main content

Deploying from GitHub Actions

The official Action is LayeroInfra/deploy-action.

name: Deploy
on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: LayeroInfra/deploy-action@v1
with:
token: ${{ secrets.LAYERO_TOKEN }}
prod: true

First — do you need the Action at all

If the repository is simply linked to a project, a push already triggers a build through the webhook and the Action is not needed: you would be adding a second copy of the same work.

You want the Action when the build has to run in your pipeline:

  • the build needs secrets the platform does not have;
  • it uses private npm packages from your own registry;
  • tests, linters or codegen must pass before publishing;
  • you build a monorepo and publish only one package out of it.

Then you build it yourself and ship the finished artifact:

- run: npm ci && npm run build
env:
API_KEY: ${{ secrets.API_KEY }}
- uses: LayeroInfra/deploy-action@v1
with:
token: ${{ secrets.LAYERO_TOKEN }}
prebuilt: dist
prod: true

The token

An ordinary login session lasts a week — no good for CI, where builds would start failing with a 401 exactly seven days later. Pipelines get a separate long-lived token instead.

  1. Open app.layero.ru/settings/cli, section "CI tokens".
  2. Give it a name (for example GitHub Actions) and press create.
  3. Copy the value immediately — it is shown once. Only a hash is stored; the token cannot be recovered.
  4. In the repository: Settings → Secrets and variables → Actions → New repository secret, named LAYERO_TOKEN.

You can revoke the token on the same page — builds using it start getting 401 right away.

:::warning The token carries account-level rights It can do everything an ordinary login can. Create a separate token per repository: then a compromise of one is contained without breaking the rest. :::

The CLI reads the token from the LAYERO_TOKEN variable before the local ~/.layero/config.json. That is deliberate: otherwise, on a developer machine with an active login, the variable would silently lose and the deploy would go to the wrong account.

Parameters

ParameterRequiredDefaultWhat it does
tokenyesLayero CI token
projectnofrom .layero/project.jsonTarget project (id or slug)
namenoProject name, used when the project is created on the first deploy
prodnofalsePublish to production
branchnoA specific branch environment; takes precedence over prod
prebuiltnoDirectory with a finished build
typenoauto-detectFramework: vite, next, astro, static
rootnoMonorepo: subdirectory to treat as the app root
working-directoryno.Where to run the deploy from
versionnolatestVersion of the layero npm package

:::danger name creates a new project on every run If the workflow is meant to keep updating the same project, pass project — it takes an id or a slug and targets one specific existing project.

name only sets the name at the moment the project is created on the first deploy. And every run is a "first deploy" whenever the repository has no .layero/project.json — which it usually does not. On a name collision the slug gets a suffix, so nothing fails loudly: the build is green, the address is simply new each time.

We walked into this in our own examples repository: 11 projects for 4 examples in a single day. :::

Step outputs

- uses: LayeroInfra/deploy-action@v1
id: deploy
with:
token: ${{ secrets.LAYERO_TOKEN }}
- run: 'echo "Published — ${{ steps.deploy.outputs.url }}"'
OutputWhat it holds
urlThe address the deploy is served at
deploy-idId of the deploy

The address also lands in the job summary, so it is visible on the run page without reading the logs.

Previews for pull requests

on: [pull_request]

jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: LayeroInfra/deploy-action@v1
with:
token: ${{ secrets.LAYERO_TOKEN }}
branch: pr-${{ github.event.number }}

Without prod the deploy goes to a preview environment and leaves production alone.

Several examples in one workflow

A matrix publishes several applications from one repository. That is how the examples repository works — four live sites are built and rolled out on every push.

strategy:
fail-fast: false
matrix:
include:
- dir: vite-react
output: dist
- dir: astro
output: dist

:::tip fail-fast: false is not paranoia Without it, one broken example cancels the rollout of all the others, including the ones already built. A single broken dependency takes down every live demo. :::

Errors you are likely to hit

prebuilt_no_index

'.next' has no index.html — nothing to serve

prebuilt is meant for static output — a directory with a ready index.html in it. A Next.js build in server mode is not such a directory.

For SSR applications do not pass prebuilt at all: ship the sources and Layero will bring the app up as a runtime. A static Next.js export (output: 'export') goes the other way — ship it with prebuilt: out.

auth_required immediately at start

The token is missing or empty. Check that the secret is named exactly LAYERO_TOKEN and is passed into the step.

The CLI used to try opening a browser login here and hung for 15 minutes until the code expired. Since version 0.8.4 it fails immediately in CI — silent waiting on a runner was worse than an honest error.

address_required / impersonation

The project name looks like somebody else's brand or like a platform service address. Pick a different one — prefixed with your organization, for example.

project_not_found

project points at a project that does not exist. Either drop the parameter (the project will be created on the first deploy) or list what you have: npx layero projects list.

Other CI systems

The Action is a wrapper around an ordinary CLI call, so any other system needs nothing but the environment variable:

LAYERO_TOKEN=... npx layero@latest deploy --prod --yes

--yes skips confirmations; without it the command in CI waits for input that will never come.