All docs
How-toTask guides

Spin up BFF with your own Perso key

In 5–10 minutes, fork vibi-bff, start it with your own Perso key, and point a physical-device vibi mobile build at it.

Assumes you have already completed "Prerequisites" in ../learning/getting-started.md.


1. Fork & clone

gh repo fork <upstream-vibi-bff-url> --clone --remote
cd vibi-bff

Or grab a zip under your account and unpack it — either works.

2. Write .env

cp .env.example .env

Keys to fill in .env (these are fail-fast at boot — a blank or malformed value exits immediately):

PERSO_API_KEY=<your_perso_api_key>
PERSO_SPACE_SEQ=<your_workspace_seq>
SEPARATION_SIGNING_SECRET=<output of: openssl rand -hex 32>
AUTH_JWT_SECRET=<output of: openssl rand -hex 32>
GOOGLE_OAUTH_CLIENT_IDS=<iOS, Android, Web client ids — comma-separated>
# Postgres — Neon free tier is enough. sslmode is now enforced (require / verify-ca / verify-full).
DATABASE_URL=jdbc:postgresql://<host>.neon.tech/neondb?sslmode=require
DB_USER=<db_role>
DB_PASSWORD=<db_password>

The BFF persists users, credits, and job history in Postgres, so the three DB* keys are required — boot fails with DATABASE_URL must not be blank otherwise. An jdbc:h2: URL is accepted for a throwaway local run.

Optional: R2 for egress-free downloads, IAP verifier keys for credit purchases, Sentry DSN for error monitoring. Full table in ../reference/environment.md.

⚠️ Never commit .env to git. It is already listed in .gitignore — verify.

3. Verify boot

./gradlew run

Success signal:

[main] INFO  Application - Responding at http://0.0.0.0:8080

Live check:

curl -i http://localhost:8080/healthz

HTTP/1.1 200 OK confirms Ktor is up and routing. (/healthz is the always-on readiness probe; the Swagger UI at /swagger only mounts when you set ENABLE_SWAGGER=true in .env.) The Perso key isn't exercised until you submit a /api/v2/separate job — if that returns 401/402 → troubleshooting.md#perso-402--payment-required.

4. External exposure options

If you only use it from emulators/simulators on the same machine, skip this step (localhost:8080 or 10.0.2.2:8080). To expose to a physical device or another computer, two common options:

Option A — LAN exposure (home / office)

ipconfig getifaddr en0    # macOS, Wi-Fi
# e.g. 192.168.1.42

Both iOS and Android physical devices must be on the same Wi-Fi. Set the mobile BFF_BASE_URL to http://192.168.1.42:8080/.

Also set .env's BFF_BASE_URL to the same value — this value is embedded in signed download URLs. A signed URL handed out externally is useless if it points at localhost.

BFF_BASE_URL=http://192.168.1.42:8080

If a firewall blocks 8080 you'll see connect timeout — on macOS, System Settings → Network → Firewall, allow Java/Gradle or turn it off briefly. Details in connect-real-device.md.

Option B — ngrok (HTTPS, reachable anywhere)

ngrok http 8080

Copy the https URL from the Forwarding https://<random>.ngrok-free.app -> http://localhost:8080 line.

# .env
BFF_BASE_URL=https://<random>.ngrok-free.app
# vibi-mobile/local.properties
BFF_BASE_URL=https://<random>.ngrok-free.app/

ngrok's free tier rotates the URL every session. For a fixed URL, use an ngrok subdomain or a cloudflare tunnel.

Side benefit: https means you can ignore iOS ATS workarounds (the http:// in option A needs extra iOS-side configuration).

Option C — Cloud Run + GitHub Actions (production)

The repo ships with everything needed to run the BFF on Google Cloud Run, fronted by a fixed HTTPS URL and redeployed on main push:

  • Dockerfile — multi-stage JDK21 → JRE21 + ffmpeg, MaxRAMPercentage=75.
  • .dockerignore / .gcloudignore — keep secrets and caches out of the build context.
  • deploy/cloud-run.sh — idempotent bootstrap (enables APIs, creates the runtime service account, loads .env into Secret Manager, runs the first gcloud run deploy).
  • .github/workflows/deploy.yml — main-branch push triggers a Workload Identity Federation login and gcloud run deploy --source .. No service account JSON stored in GitHub. The runtime SA, env vars, secrets, and resource spec are declared inline so each deploy re-wires them; the GitHub UI is the single source of truth for env updates.

Quickstart:

# 1. First-time bootstrap from your laptop (creates the SA + secrets + first Cloud Run revision)
cd vibi-bff
./deploy/cloud-run.sh

# 2. One-time WIF setup so GitHub Actions can deploy without a JSON key
# Follow deploy/GITHUB_ACTIONS_SETUP.md (workload-identity-pools create + provider + SA binding)

# 3. Configure GitHub Settings → Secrets and variables → Actions

What goes where (Settings → Secrets and variables → Actions):

TypeKeySource / purpose
SecretGCP_WIF_PROVIDEROutput of step 2 — the WIF provider resource path.
SecretGCP_SA_EMAILDeploy-time SA (allowed to gcloud run deploy).
SecretGCP_PROJECT_IDCloud Run hosting project.
SecretGCP_RUNTIME_SA_EMAILRuntime SA attached to the Cloud Run service.
SecretGOOGLE_OAUTH_CLIENT_IDSComma-separated iOS / Android / Web client ids.
VariableCORS_ALLOWED_ORIGINSPlain comma list — visible in workflow logs intentionally.
VariableBFF_BASE_URLOptional. Set when you own a custom domain (https://api.example.com); leave blank to self-reference the Cloud Run-issued URL.
VariableR2_BUCKET, R2_ACCOUNT_IDOptional. When R2_BUCKET is set, big render / separation outputs 302-redirect to a Cloudflare R2 SigV4 presigned URL — R2 egress is free, so this decouples Cloud Run egress and zeros out its cost. R2_ACCOUNT_ID is the 32-char hex from the Cloudflare dashboard URL.
Secret ManagerPERSO_API_KEY, PERSO_SPACE_SEQ, AUTH_JWT_SECRET, SEPARATION_SIGNING_SECRET, DATABASE_URL, DB_USER, DB_PASSWORD, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY (+ IAP_APPLE_ISSUER_ID / IAP_APPLE_KEY_ID / IAP_APPLE_PRIVATE_KEY when billing is live)Created by deploy/cloud-run.sh from your .env. The workflow mounts them via --set-secrets. R2 credentials are an API token (Object Read & Write) issued in the Cloudflare dashboard.

After that, git push origin main ships a new revision. Two things worth flagging on first read of deploy.yml:

  • ^@^ delimiter--set-env-vars uses ^@^ as the entry separator instead of the default ,, because GOOGLE_OAUTH_CLIENT_IDS itself contains commas. Switching the delimiter to @ lets the comma-bearing value stay inline.

  • Scale-to-zero runtime spec — the deploy step pins --cpu 1 --memory 2Gi --cpu-boost --concurrency 4 --timeout 600 --min-instances 0 --max-instances 2 --no-cpu-throttling --session-affinity in us-central1. The flags that matter:

    • --min-instances 0 — the service costs nothing while idle (it cold-starts on the next request).
    • --no-cpu-throttling — keeps CPU allocated for the instance's whole lifetime, so background ffmpeg renders and Perso polling keep advancing between requests.
    • --session-affinity — pins a polling client to the instance holding its job.

    This pairs with Neon's idle compute suspend (the BFF drains its DB connection pool when idle), so a quiet deployment scales the app and the database toward zero cost.

  • Real client IP behind the proxy — set the RATE_LIMIT_TRUSTED_PROXY_HOPS Variable to 1 so the rate-limiter reads the real client IP through Cloud Run's proxy.

The runtime credential is the Cloud Run attached service account (GCP_RUNTIME_SA_EMAIL). Memory-tier vs upload-size: scale MAX_UPLOAD_FILE_SIZE_MB down when running on --memory 1Gi, up on 4Gi+.

The full walkthrough — WIF bootstrap commands, IAM roles per secret, R2 bucket creation + API token in the Cloudflare dashboard, and a table mapping five common errors (invalid_target, unauthorized_client, iam.serviceAccounts.getAccessToken denied, run deploy: Permission denied, 500 only on /download after a successful deploy) to their root cause — lives in vibi-bff/deploy/GITHUB_ACTIONS_SETUP.md.

Lifecycle / retention. R2 lifecycle rules are managed in the Cloudflare dashboard (Settings → Object lifecycle rules), not in deploy/cloud-run.sh. The repo ships vibi-bff/deploy/r2-lifecycle.json with a 7-day expire rule — apply via dashboard or aws s3api put-bucket-lifecycle-configuration with the R2 endpoint override.

5. Wire up mobile

# vibi-mobile/local.properties
sdk.dir=/Users/<you>/Library/Android/sdk
BFF_BASE_URL=http://192.168.1.42:8080/   # or the ngrok https URL

After changing the value, a rebuild is required:

cd vibi-mobile
./gradlew :cmp:assembleDebug --no-configuration-cache

For iOS, Clean Build Folder in Xcode and then Run.

6. Verify

Launch the app → upload a video → drag a range and tap "이 구간 음원분리" (Separate this range).

You should see lines like this in the BFF console:

[POST] /api/v2/separate
[GET]  /api/v2/separate/sep-...
[GET]  /api/v2/separate/sep-.../stem/voice_all

If this is the first call from a physical device or a different computer, note that per-user Perso workspace usage counts against the BFF's PERSO_API_KEY quota.


Common pitfalls

SymptomWhere to go
Key validation fails on BFF boottroubleshooting.md#perso_api_key-must-not-be-blank-on-boot
connect timeout from mobileconnect-real-device.md
iOS rejects cleartext httpconnect-real-device.md#ios-ats
Perso 402troubleshooting.md#perso-402--payment-required

See also

  • Background on the flow decisions: ../explanation/why-bff.md
  • Full environment variable set: ../reference/environment.md
  • BFF README: vibi-bff/README.md (this doc takes the mobile / external-exposure angle; the README takes the BFF's own angle)
  • Cloud Run deploy details: vibi-bff/deploy/GITHUB_ACTIONS_SETUP.md (referenced from Option C above)