Deploying to TaruviBase with GitHub Actions
TaruviBase frontend workers host your built application and accept new builds over the API, so a build job can publish a release without anyone running a local build or clicking Upload in the Console. GitHub Actions supplies the trigger: merge to a branch, and that branch's deploy runs.
This guide wires one workflow file to one TaruviBase site per branch, using GitHub environments to hold each site's values. After the setup, merging a pull request builds your app, uploads it to your TaruviBase frontend worker, and imports any backend configuration you keep in the repository. The branch you merged to decides which TaruviBase site receives the deploy.
Three steps: collect your values, add them to GitHub, copy the workflow file. Budget about ten minutes for the first branch and two for each one after that.
Before you start: a TaruviBase app you can open in the Console, a GitHub repository you can change settings on, and a build script that writes to dist/. The GitHub CLI is optional — every step below shows both the browser and the command-line route.
Step 1 — Collect your values
Each TaruviBase site issues its own credentials. In TaruviBase Console, open your app, go to Settings → Connect, select Generate API Key, and copy these three values from the Environment tab.
| Value | What it is | Example |
|---|---|---|
| Site URL | The TaruviBase site your app lives on. Not a credential, and it ends up in your browser bundle anyway. | https://YOUR_SITE_HOST |
| API key | Authenticates the deploy. Treat it like a password. Give it an expiration when you create it, and update the GitHub secret whenever you generate a new key. | TARUVI_API_KEY |
| App slug | Identifies the app on that site. | APP_SLUG |
Copy all three from the same Connect page, in one sitting. An API key exists only on the site that issued it, so a key from one site paired with another site's URL fails as Invalid token. — which reads like an expired key and sends people off regenerating one, and regenerating cannot fix it.
Repeat for each instance you deploy to. A typical setup has two: one for dev, one for main.
Step 2 — Add them to GitHub
GitHub environments hold one set of values per branch. This is what lets a single workflow file deploy to different TaruviBase sites without any branch logic in the YAML — the job asks for the environment named after the current branch, and GitHub hands it that environment's values.
Create one environment per branch, named exactly after the branch: main and dev.
Then add the same four names to each environment, with values from that instance's Connect page:
| Name | Kind | Value |
|---|---|---|
TARUVI_API_KEY | Secret | Your API key |
TARUVI_SITE_URL | Variable | Your site URL |
TARUVI_APP_SLUG | Variable | Your app slug |
TARUVI_APP_TITLE | Variable | Display name, such as My App |
Variables are plaintext: unmasked in logs, visible in the Settings UI, and readable through the REST API by anyone with read access to the repository. Secrets are write-only and masked in logs. The two columns sit side by side in the UI and look interchangeable.
Keeping the site URL and app slug as variables is deliberate. Neither is a credential, and an unredacted site URL is what tells you which site a failing deploy actually reached.
Using the GitHub web UI
- Go to Settings → Environments → New environment and name it after the branch.
- Under Environment secrets, select Add secret and add
TARUVI_API_KEY. - Under Environment variables, select Add variable and add
TARUVI_SITE_URL,TARUVI_APP_SLUG, andTARUVI_APP_TITLE. - Repeat for the second branch.
Using the GitHub CLI
The CLI has no dedicated command for creating an environment, so create it through the API first — secret and variable commands fail against an environment that does not exist yet:
gh api --method PUT "repos/{owner}/{repo}/environments/main"
gh api --method PUT "repos/{owner}/{repo}/environments/dev"
Add the key without a value on the command line. Omitting --body makes gh prompt for the value and read it without echoing it, which keeps the key out of your shell history:
gh secret set TARUVI_API_KEY --env main
gh secret set TARUVI_API_KEY --env dev
Then add the three variables per environment:
gh variable set TARUVI_SITE_URL --env main --body "https://YOUR_SITE_HOST"
gh variable set TARUVI_APP_SLUG --env main --body "APP_SLUG"
gh variable set TARUVI_APP_TITLE --env main --body "My App"
Replace YOUR_SITE_HOST and APP_SLUG with the values from the Connect page, then repeat with --env dev and that instance's values.
Confirm what landed where before moving on. Neither command prints secret values:
gh secret list --env main
gh variable list --env main
Run gh api "repos/{owner}/{repo}/environments" --jq '.environments[].name' to list the environments themselves and check the names match your branches character for character.
Step 3 — Create the workflow
Create .github/workflows/deploy.yml and paste this in as-is. No edits needed — every environment-specific value is resolved at runtime.
name: Deploy to TaruviBase
on:
push:
branches: [main, dev]
workflow_dispatch:
concurrency:
group: deploy-${{ github.ref_name }}
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
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 }}
- name: Create ZIP
run: cd dist && zip -r ../dist.zip . && cd ..
- name: Deploy frontend worker
id: frontend
uses: Taruvi-ai/taruvi-action/frontend-worker@v1
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
id: backend
uses: Taruvi-ai/taruvi-action/backend@v1
with:
site-url: ${{ vars.TARUVI_SITE_URL }}
api-key: ${{ secrets.TARUVI_API_KEY }}
config-dir: .taruvi-backend
- name: Summary
run: |
echo "Frontend: ${{ steps.frontend.outputs.frontend-url }}"
echo "Backend: ${{ steps.backend.outputs.status }}"
The highlighted line is the whole trick: environment: ${{ github.ref_name }} names the environment after the branch being pushed, so dev and main pull different credentials from identical YAML.
Three details worth knowing. The build step never receives TARUVI_API_KEY: anything passed to a frontend build can end up in the published JavaScript, where anyone can read it. Only the deploy steps use the key. Install and build are separate steps so the build receives environment values without handing them to npm ci, which executes third-party install scripts. And concurrency cancels a superseded run when two merges land close together, so the newer build wins rather than racing the older one.
Commit the file to both main and dev. A workflow only runs from the branch it exists on.
Verify the first deploy
Merge something, or trigger a run by hand — workflow_dispatch in the trigger list is what makes the manual route available:
gh workflow run deploy.yml --ref dev
gh run watch
gh run watch follows the run to completion in your terminal. When it finishes, read the deployed URL out of the summary step:
gh run view --log | grep -A2 "Frontend:"
Open that URL. If the run failed, gh run view --log-failed prints only the failing step's output, which is usually the build.
What happens on merge
- The frontend is built and zipped.
- The build is uploaded to your app's frontend worker and activated. If the app has no worker yet, one is created at
{app-slug}-{branch}. - If a
.taruvi-backend/directory exists, its contents — an unzipped app export from TaruviBase Console — are imported into the site. If not, the step reportsskippedand the run still passes. - The deployed URL is printed in the Summary step.
See Frontend workers to manage the worker, its builds, and its address.
Uploading and activating are separate operations, and the action treats a failed activation as a failed deploy. A green run therefore means the build you just pushed is the one being served — not that a build was uploaded and something else is still live.
Adjustments
Different branches — change the trigger list and your environment names together, so each branch still has an environment with an identical name:
on:
push:
branches: [main, staging]
Frontend only — delete the Import backend config step.
Backend only — delete the setup-node, install, build, ZIP, and frontend steps.
Backend config in another folder — change config-dir.
Skip deploys for documentation-only changes:
on:
push:
branches: [main, dev]
paths-ignore:
- '**.md'
More environments and sites
The workflow resolves credentials at runtime from environment: ${{ github.ref_name }}. Adding a site means adding a branch and an environment with the same name. The YAML does not change.
Example — four environments across four sites:
| Branch | Environment | Site URL |
|---|---|---|
main | main | Production site address |
staging | staging | Staging site address |
qa | qa | QA site address |
dev | dev | Development site address |
- Create the branches.
- Create one GitHub environment per branch, named identically.
- In each, add
TARUVI_API_KEYas a secret plusTARUVI_SITE_URL,TARUVI_APP_SLUG, andTARUVI_APP_TITLEas variables, taken from that site's Connect page. - Add the branches to the trigger:
on:
push:
branches: [main, staging, qa, dev]
workflow_dispatch:
Same names everywhere, different values per environment. Nothing else in the workflow is environment-aware.
Scripting the whole set with the GitHub CLI
Creating four environments by hand is repetitive. This loop creates each one and sets its variables, prompting for the key once per environment so no key is stored in the script:
for env in main staging qa dev; do
gh api --method PUT "repos/{owner}/{repo}/environments/$env"
gh variable set TARUVI_SITE_URL --env "$env" --body "https://YOUR_SITE_HOST"
gh variable set TARUVI_APP_SLUG --env "$env" --body "APP_SLUG"
gh variable set TARUVI_APP_TITLE --env "$env" --body "My App"
gh secret set TARUVI_API_KEY --env "$env"
done
Replace YOUR_SITE_HOST and APP_SLUG with each site's own values from its Settings → Connect page.
Environment name does not match the branch name? Do not map them in YAML. Rename the environment, or add a new one, so it matches the branch exactly. The workflow stays untouched.
Deploying one branch to several sites — run the job once per environment with a matrix. Each entry picks up its own secrets:
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [eu-prod, us-prod, apac-prod]
environment: ${{ matrix.target }}
# ...same steps as above
No dedicated branch, deploy on demand — add a choice input and prefer it over the branch name:
on:
workflow_dispatch:
inputs:
target:
type: choice
options: [dev, qa, staging, main]
jobs:
deploy:
environment: ${{ inputs.target || github.ref_name }}
Add required reviewers on production environments (Settings → Environments → Required reviewers) so those deploys pause for approval. Deployment branch rules on an environment also stop the wrong branch from ever reaching a site.
Troubleshooting
| Symptom | Fix |
|---|---|
Invalid token. / 401 / 403 | TARUVI_SITE_URL and TARUVI_API_KEY are from different sites. Re-copy both from one Connect page. Regenerating the key will not help. |
| 404 on app settings | TARUVI_APP_SLUG is wrong, or the app is on a different site than TARUVI_SITE_URL. |
| Secrets come through empty | The environment name must match the branch name exactly (main, not Main). Check the values are on the environment, not only at repository level: gh secret list --env main. |
Frontend Worker with this Slug already exists. | The worker exists but the app is not pointing at it. Open the app's settings page in the TaruviBase Console and set that worker as the default frontend worker. |
dist.zip: No such file | The build produced no dist/. Read the Build frontend step log: gh run view --log-failed. |
Backend step says skipped | No .taruvi-backend/ directory in the repository. Expected if you have no backend configuration. |
| Nothing runs on merge | The workflow file must exist on the target branch, and the trigger is push, not pull_request. |
Environment secrets reach the job on push events. Do not switch the trigger to pull_request: for those events github.ref is refs/pull/<n>/merge, which no deployment branch policy matches, so GitHub withholds the environment's values. A merged pull request pushes to its target branch, which gives the same result.