Deploy with GitHub Actions
Set up a GitHub Actions workflow that builds your app and deploys it to
TaruviBase every time you push to a branch. Each branch deploys to its own
TaruviBase site — for example dev to your development site and main to
production — from one workflow file, with a GitHub environment per branch
holding that site's values.
Each run uploads your frontend build to the app's frontend worker, and imports any backend configuration you keep in the repository. Setting up the first branch takes about ten minutes.
Before you begin#
- A TaruviBase app on each site you deploy to, and a TaruviBase Console account in each site's organization. The API key you generate acts as that account, and a key from an account outside the organization can't deploy.
- A GitHub repository whose settings you can change. Environments in a private repository need GitHub Pro, Team, or Enterprise; on GitHub Free they are available only in public repositories. For a private repository on GitHub Free, see Deploy without environments.
- A frontend that builds with
npm run buildintodist/, withindex.htmlat the root ofdist/, and a committedpackage-lock.json. The zipped build must stay within the archive limits, including the 10 MB upload limit.
GitHub-hosted ubuntu-latest runners have everything the deploy actions use.
On a self-hosted runner, install bash, curl, jq, and zip.
The TaruviBase Refine starter template needs two changes before it works with this workflow:
- Its
.gitignoreexcludespackage-lock.json. Remove that line, runnpm install, and commit the lock file.npm ciand the dependency cache in the workflow both need it. - Its
src/taruviClient.tspassesTARUVI_API_KEYtonew Client()and stops the app withMissing required environment variable: TARUVI_API_KEYwhen the build doesn't have it. Don't give the build your key to get past this: anything compiled into a frontend can be read by everyone who opens it. Create the client without a key, as in Create the TaruviBase client, and add a sign-in page.
Get your site values#
Do this once for each site you deploy to.
- In TaruviBase Console, open your app on that site and go to Settings → Connect.
- Select Generate API Key. Give the key a name you'll recognize, such as
github-deploy-main, and choose when it expires. The default is 30 days. Deploys stop working when the key expires, so pick a period you'll renew in time. - Copy the key before you leave the page. It's shown only once.
- Open the Environment tab. It lists the three values the workflow uses:
TARUVI_SITE_URL,TARUVI_APP_SLUG, andTARUVI_API_KEY.
Take all three from the same Connect page. An API key exists only on the site
that issued it, so a key paired with another site's URL is rejected with
Invalid token. — the same message an expired key gives.
Add the values to GitHub#
A GitHub environment holds one set of values, and the workflow asks for the environment named after the branch being pushed. That's what lets one workflow file deploy each branch to a different TaruviBase site.
Create one environment per branch, with the same name as the branch — for
example dev and main. Environment names aren't case-sensitive. Add these
names to each environment, using that site's values:
| Name | Kind | Value |
|---|---|---|
TARUVI_API_KEY | Secret | The API key |
TARUVI_SITE_URL | Variable | The site URL |
TARUVI_APP_SLUG | Variable | The app slug |
TARUVI_APP_TITLE | Variable | Optional. A display name, such as My App. Only needed if your build reads it; the Refine starter template does. |
Variables are plain text. Anyone who can open the environment's settings can read them, and they are printed unmasked in run logs. Secrets are write-only and masked in logs. The two lists sit side by side in GitHub and look alike.
The site URL and app slug are variables on purpose: neither is a credential, and seeing the real site URL in a log tells you which site a failing deploy reached.
- In the repository, go to Settings → Environments → New environment, and name it after the branch.
- Under Environment secrets, select Add environment secret and add
TARUVI_API_KEY. - Under Environment variables, select Add environment variable and
add
TARUVI_SITE_URLandTARUVI_APP_SLUG, plusTARUVI_APP_TITLEif your build reads it. - Repeat for each branch.
Deploy without environments#
On GitHub Free, a private repository has no environments. Add the same names
under Settings → Secrets and variables → Actions as repository secrets and
variables instead, and remove the environment block from the workflow below.
Every branch in the trigger then deploys to the same site, so list only one
branch. The deployed address then appears only in the deploy step's log.
Add the workflow#
Create .github/workflows/deploy.yml with this content. It needs no edits:
every site-specific value comes from the environment at run time.
name: Deploy to TaruviBase
on:
push:
branches: [main, dev]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: deploy-${{ github.ref_name }}
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: ${{ github.ref_name }}
url: ${{ steps.frontend.outputs.frontend-url }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build frontend
run: npm run build
env:
TARUVI_SITE_URL: ${{ vars.TARUVI_SITE_URL }}
TARUVI_APP_SLUG: ${{ vars.TARUVI_APP_SLUG }}
TARUVI_APP_TITLE: ${{ vars.TARUVI_APP_TITLE }}
VITE_TARUVI_SITE_URL: ${{ vars.TARUVI_SITE_URL }}
VITE_TARUVI_APP_SLUG: ${{ vars.TARUVI_APP_SLUG }}
- name: Create ZIP
run: cd dist && zip -qr ../dist.zip .
- name: Deploy frontend worker
id: frontend
uses: Taruvi-ai/taruvi-action/frontend-worker@main
with:
site-url: ${{ vars.TARUVI_SITE_URL }}
api-key: ${{ secrets.TARUVI_API_KEY }}
app-slug: ${{ vars.TARUVI_APP_SLUG }}
zip-path: dist.zip
branch-name: ${{ github.ref_name }}
- name: Import backend config
uses: Taruvi-ai/taruvi-action/backend@main
with:
site-url: ${{ vars.TARUVI_SITE_URL }}
api-key: ${{ secrets.TARUVI_API_KEY }}
config-dir: .taruvi-backend
The highlighted line does the routing: it names the environment after the
branch being pushed, so dev and main get different values from the same
YAML. The url line puts the deployed address on the run page.
Details worth knowing:
- Set the variable names your build reads. The Refine starter template
reads
TARUVI_*. A Vite app set up as in the JavaScript SDK guide readsVITE_TARUVI_*; without those, the deployed page fails withAPI URL is requiredeven though the run passes. The build step above sets both. - The build never receives
TARUVI_API_KEY. Anything passed to a frontend build can end up in the published JavaScript. Only the deploy steps use the key. - Install and build are separate steps, so the build gets its values
without handing them to
npm ci, which runs third-party install scripts. concurrencycancels a superseded run when two pushes land close together, so the newer build wins instead of racing the older one.
Commit the file to every branch in the trigger. A push runs the workflow file from the branch that was pushed.
Run the first deploy#
Push to dev, or start a run by hand. The workflow_dispatch trigger is what
allows a manual run:
gh workflow run deploy.yml --ref dev
gh run watch
gh run watch asks which run to follow and shows it until it finishes. When it
passes, the deployed address appears next to the deploy job on the run page,
and under Deployments on the repository's home page. Open it to check the
app.
If the run fails, gh run view --log-failed prints only the failing step's
output. See Troubleshooting for the messages the deploy
steps print.
What each run does#
-
Builds the frontend and zips
dist/. -
Uploads the build to the app's default frontend worker. The build is live as soon as the upload finishes.
On a site's first run the app has no default worker yet, so the action creates one for the app, with an address based on the app slug and branch name (for example
APP_SLUG-dev), and makes it the app's default. Later runs update the same worker, so the address doesn't change. If a worker with that name already exists for the app, the action reuses it. -
If
.taruvi-backend/exists and isn't empty, imports it into the site. Otherwise the step reportsskippedand the run still passes. See Keep backend configuration in the repository.
A failed step fails the run, and the steps after it don't run. A passing run means the build you pushed is the one being served. The backend import runs after the frontend upload, so if the import fails, the new frontend is already live.
To manage the worker, its builds, and its address, see Frontend workers.
Keep backend configuration in the repository#
The backend step imports an app export — data tables, functions, roles, secret names, and the app's other configuration — so the same configuration reaches every site you deploy to.
- In TaruviBase Console, open the app's overview and select Export App.
- Under Modules to Export, make sure Frontend Workers isn't selected.
- Unzip the export into
.taruvi-backend/at the root of the repository, so thatmanifest.jsonsits directly inside it, and commit the folder.
The workflow deploys your frontend itself. An export that includes frontend workers makes the import replace the build the frontend step just uploaded with the exported one, and move the worker to a temporary address.
How the import behaves:
- The target app comes from the export, not from
TARUVI_APP_SLUG. The import updates that app on the site, or creates it if it doesn't exist. - Secret values are never exported. Where a secret doesn't exist on the
site yet, the import creates it with the value
PLACEHOLDER - Update with actual value; set the real value in that site's Console. Existing secret values aren't changed. - Every push imports the folder again and updates the resources it contains. Make changes to those resources in the repository; a change made in a site's Console can be overwritten by the next deploy.
- The import finishes within the step, and the step prints the result, including any warnings.
Customize the workflow#
Different branches. Change the trigger list and the environment names together, so each branch still has an environment with the same name:
on:
push:
branches: [main, staging]
More sites. Add a branch and an environment with the same name, with that site's values, and add the branch to the trigger. The rest of the workflow doesn't change.
Frontend only. Delete the Import backend config step.
Backend only. Delete the setup-node, install, build, ZIP, and frontend
steps, and the url line.
Backend configuration in another folder. Change config-dir.
pnpm or Yarn. Change cache and the install command to match, for example
cache: 'pnpm' and pnpm install --frozen-lockfile, after a step that
installs pnpm.
Skip deploys for documentation-only changes:
on:
push:
branches: [main, dev]
paths-ignore:
- '**.md'
One branch to several sites. Run the job once per environment with a matrix; each entry gets its own environment's values:
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [eu-prod, us-prod, apac-prod]
environment:
name: ${{ matrix.target }}
url: ${{ steps.frontend.outputs.frontend-url }}
# ...same steps as above
Deploy on demand. Add a choice input and use it before the branch name:
on:
workflow_dispatch:
inputs:
target:
type: choice
options: [dev, qa, staging, main]
jobs:
deploy:
environment:
name: ${{ inputs.target || github.ref_name }}
Protect production. Required reviewers pause a deploy until someone approves it, and deployment branch rules stop the wrong branch from reaching a site. Set both under Settings → Environments. Required reviewers are available for public repositories, and for private repositories on GitHub Enterprise.
Troubleshooting#
The API key is rejected#
The deploy step fails with Could not read settings for app 'APP_SLUG' (HTTP 401)
and Credentials were rejected, and the response contains Invalid token.
The key has expired, or TARUVI_SITE_URL and TARUVI_API_KEY come from
different sites. Check the key's expiry on the site's Settings → API Tokens
page. If it has expired, generate a new one and update the TARUVI_API_KEY
secret. If it hasn't, copy both values again from one Connect page.
The app isn't found#
The deploy step fails with App 'APP_SLUG' not found on ….
TARUVI_APP_SLUG is wrong, or the app is on a different site than
TARUVI_SITE_URL.
The site can't be reached#
The deploy step fails with Could not reach … to read app settings (no HTTP response).
If no address appears between reach and to, the job got no values: see
The values come through empty. Otherwise
check that TARUVI_SITE_URL is the address from the Connect page.
The values come through empty#
The environment's name doesn't match the branch. When a job asks for an
environment that doesn't exist, GitHub creates an empty one with that name
instead of failing, so the job gets none of the values you set up. Look under
Settings → Environments for an unexpected environment named after the
branch, and add the values to it — or delete it and rename your environment to
match the branch. Run gh secret list --env BRANCH to check what an
environment holds.
Deploying needs administrative access#
The upload fails with HTTP 403 and
You need to have administrative access to perform this operation.
The key belongs to an account that isn't in the site's organization, or that has been removed from it. Generate a new key from a TaruviBase Console account in the organization, and update the secret.
The build is rejected#
The upload fails with HTTP 400 and one of these messages:
Archive must contain an index.html at the root— the ZIP holds the wrong folder. If your build writes somewhere other thandist/, change theCreate ZIPstep to use that folder.File size exceeds maximum allowed size of 10.0MB— the zipped build is too large. See the archive limits.Archive contains environment files at the root— a.envfile ended up in the build output, usually frompublic/. Remove it; it may hold secrets.
dist.zip isn't created#
The Create ZIP step fails with cd: dist: No such file or directory. The
build failed or wrote somewhere else. Read the Build frontend step's log.
The worker's address is taken#
The first deploy fails with Subdomain 'APP_SLUG-BRANCH' is already in use,
or with Worker … already exists and belongs to app '…'.
Another worker already uses the address the action would create. Set
branch-name in the workflow to a different value, such as
${{ github.ref_name }}-web. Alternatively,
create a worker in the Console
with an address of your choice, and select it as the app's default under the
app's Settings → Frontend. Later runs then update that worker.
The build is live, but the step failed#
The deploy step fails with could not be set as the default for app '…'.
The build is already being served from the new worker, but it isn't the app's default yet. Run the workflow again: the action reuses the worker and retries.
The deployed page is blank#
The run passed, but the browser console shows API URL is required or
Missing required environment variable. The build didn't get the variable
names your app reads; see
Set the variable names your build reads.
The backend step says skipped#
There's no .taruvi-backend/ folder in the repository, or it's empty. That's
expected if you have no backend configuration.
The backend import fails#
The step fails with Failed to import backend configuration (HTTP 400), and
the response lists the problems. Missing manifest.json in ZIP file means the
export was unzipped one folder too deep: manifest.json must sit directly in
.taruvi-backend/.
Nothing runs on push#
The workflow file must exist on the pushed branch, and the trigger must be
push.
Don't switch the trigger to pull_request. For those events
github.ref_name is NUMBER/merge, so the job would ask for an environment
by that name, which holds none of your values. Merging a pull request pushes
to its target branch, which runs the deploy.