Cloud setup
Develop against a hosted environment instead of a local Shell — Dev by default (UAT/prod use the same shape). The Shell and platform already run in the cloud; you run only your app locally, point it at Dev, and register it there. The cloud Shell loads your local app in your browser — so keep your app's API and UI running, the UI as a production build, in Chrome (why).
You still run the app locally, so have the prerequisites installed — Docker, Bun, Node 20.12+ and git — and run the check there first.
| Dev Shell UI | veni.dev.venizia.ai |
| Dev Shell API | https://shell-api.dev.venizia.ai/api |
First — get a Dev account
Dev has no seeded logins (unlike local). Request a Dev account with the developer role from the platform team — the developer role is what unlocks self-service app registration (Developer mode / My Apps). Once you're provisioned, sign in at veni.dev.venizia.ai via Keycloak / Google SSO.
The counter-intuitive part — the cloud Shell loads your local app in your browser, and your local API talks back to the cloud Shell to verify tokens:
1 · Get a Dev token
You need a Dev token to register an app. Two ways to get one — pick whichever suits you.
Install the veni CLI and let it sign you in (no local builder needed; also what you want if you're scaffolding a new app):
curl -fsSL https://veni-builder-api.dev.venizia.ai/api/cli/install.sh | bashIt prints a short code, opens the Dev Shell in your browser to approve it, and saves a 90-day token — no copy-paste. veni lands in ~/.local/bin; put that on your PATH, then run bare veni to check it resolves. The full scaffold path is in Build a satellite.
Or mint one by hand — signed in to the Dev Shell, Profile → Personal access tokens → Create (shown once). Use it to register, to install the CLI without the browser step (-H "X-Veni-Token: <token>"), or in CI as VENI_TOKEN.
2 · Clone the app & start its services
The app brings its own backing services — a database and its own Redis (cache + RBAC) — in its own repo. Nothing here comes from the cloud; only the Shell does.
Clone the app and start its services — HRM is the example; every app follows the same shape (swap the name, port and DB). Building your own instead? Scaffold it first, then continue here. From your workspace folder:
git clone https://github.com/VENIZIA-AI/hrm.git
cd hrm
docker compose up -d hrm-db hrm-redis # DB + Redis — both ship in the hrm repo3 · Configure & run — pointed at Dev
The app answers on its own port, talks to its own database, and can prove that a token the Dev Shell issued is genuine.
Configure. From the app folder, copy both envs — the API's and the UI's:
cp api/.env.example api/.env
cp ui/.env.example ui/.envapi/.env — the copied defaults are already wired for this app's own database, Redis and port (3012); every line is documented in .env.example. Two things are yours to set. First the signing secret, same as local:
sed -i.bak "s|^APP_ENV_JWT_SECRET=.*|APP_ENV_JWT_SECRET=$(openssl rand -hex 32)|" api/.env && rm -f api/.env.bakThen the three lines that point at Dev instead of a local Shell — the only real difference from Local:
APP_ENV_SHELL_JWKS_URL=https://shell-api.dev.venizia.ai/api/.well-known/jwks.json # verifies Dev tokens
APP_ENV_SHELL_GRPC_URL=https://shell-api.dev.venizia.ai/api # call the Dev Shell
APP_ENV_CORS_ORIGINS=https://veni.dev.venizia.ai # allow the Dev Shell originUsing veni register? The three Shell lines are set for you
veni register --shell https://shell-api.dev.venizia.ai writes all three lines above into api/.env itself — that block is what you'd set by hand on the no-CLI path. You still set the secret yourself, and you still restart the API afterwards.
It leaves ui/.env alone, though — those Shell URLs are always yours to set.
ui/.env — the copied defaults keep pointing at your local API (VITE_API_URL, port 4175, service id hrm), which is right: the UI is still yours. Only its two Shell links move to Dev:
VITE_SHELL_API_URL=https://shell-api.dev.venizia.ai/api # Dev Shell API
VITE_SHELL_URL=https://veni.dev.venizia.ai # Dev Shell UI — where a 401 sends youRun. API and UI are separate processes — a terminal each. Copy one block per terminal. Note the UI is built and previewed, not dev — see below:
# Terminal 1 · API :3012 — from hrm/
cd api && bun install && bun run db:migrate && bun run dev# Terminal 2 · UI :4175 — from hrm/ — a build, never `bun run dev`
cd ui && bun install && bun run build && bun run previewCheck it booted: curl http://localhost:3012/api/health → {"status":"ok"}. Restart the API after any .env change.
The UI must be a production build here — bun run dev will not do
This is the one place Cloud differs from Local, and it catches everyone.
Against a local Shell, bun run dev works because that Shell is itself a Vite dev server. The Dev Shell is a production build, and a dev-mode remote is a dev artifact — it ships an HMR shim and imports out of Vite's on-the-fly module graph (/node_modules/.vite/deps/…). A production host has no business loading that. So serve a production build of your own — the bun run build && bun run preview from the Run step above, never bun run dev.
preview serves on the same :4175 as dev, so the remote entry you register doesn't change. The trade: no hot reload — re-run build after every UI change, then refresh the Shell.
Because it is a build, ui/.env is baked in when you run build — set it first, and re-build after touching it. Skip it and the UI compiles happily with empty URLs, then fails at runtime with a token exchange to nowhere.
Open the Dev Shell in Chrome
The Shell is HTTPS and your app is http://localhost:4175. Chrome treats localhost as a trusted origin and loads it anyway; other browsers can refuse it as mixed content and your app silently never appears. Use Chrome for local-against-Dev work.
4 · Register & open
The app is running on your machine, but the Dev Shell has never heard of it. It should appear on your launchpad and load at /<you>-hrm — signed in, no second login.
Your Dev account has the developer role (step 1), so register your own copy from the Dev Shell UI — nothing to install:
Open veni.dev.venizia.ai and flip the sidebar toggle End-user → Developer. You land on My Apps.
Paste HRM's two values:
Field Value Local UI URL http://localhost:4175App slug hrmRegister app — a local URL is allowed automatically here, no tick box.
Done — reload the Dev Shell and HRM is on your launchpad. Open https://veni.dev.venizia.ai/<you>-hrm in Chrome, keeping the API and the UI preview running: you're already signed in, no second login, and the cloud Shell loads your local remoteEntry.js.
Your slug is prefixed — that's expected
Self-service prefixes the slug with your Dev username and registers a private draft only you see, so many developers each register their own hrm without colliding. The unprefixed /hrm needs the admin role, which a Dev developer account doesn't have. Full role split: Register.
Prefer the CLI — or registering another app?
CLI — one command from the app root, using the Dev PAT and veni from step 1:
veni register --shell https://shell-api.dev.venizia.ai # PAT → self-service: mounts at /<you>-hrm, private draftDev is SSO-only, so there's no --user password login — and a PAT is always self-service (why). It also rewrites api/.env (JWKS, gRPC, CORS origin), so restart the app's API after.
Another app — same three steps, different values. Drive: local UI URL http://localhost:4176, slug drive, opens at /<you>-drive. Full reference + troubleshooting: Register.
Another environment — UAT/prod use the same shape; swap the host for shell-api.uat.venizia.ai / shell-api.venizia.ai.
Want more apps? Repeat steps 2–4 for each — they never collide (each owns its own database and ports).
Start over — reset the app locally
The Dev Shell is cloud-hosted, so there's nothing to reset there — only your local app holds state. From the app folder:
docker compose down -v # drops hrm-db and hrm-redis dataThen re-run bun run db:migrate (step 3) on the empty database. Stop the API and UI with Ctrl-C in their terminals; because the UI serves a build, re-run bun run build after any change.
Common snags
| Symptom | Fix |
|---|---|
API crash-loops: Environment validation failed: APP_ENV_JWT_SECRET | Blank in .env.example; set APP_ENV_JWT_SECRET=$(openssl rand -hex 32) in the app's api/.env. |
App API: relation … does not exist | You skipped bun run db:migrate in api/. |
| App can't reach its DB | Its Postgres isn't up (docker compose up -d hrm-db hrm-redis), or APP_ENV_DATABASE_URL uses :5432 instead of the mapped host port :15434. |
| App won't load on Dev | Your local API + UI must be running; My Apps allows the local URL automatically — register, then reload the Dev Shell. |
Registered, but the path isn't /hrm | Self-service prefixes your Dev username → /<you>-hrm, a private draft. That's expected; open that path. |
| No My Apps in the sidebar | Flip the sidebar End-user → Developer toggle. Missing entirely? Your Dev account lacks the developer role — request it from the platform team. |
Blank frame, console errors about HMR or /node_modules/.vite/… | The UI is running on bun run dev. The Dev Shell is a production host — run bun run build && bun run preview instead. |
| Nothing loads, and the console says the resource was blocked | Not Chrome. An HTTPS Shell loading http://localhost is refused as mixed content by some browsers — open the Dev Shell in Chrome. |
| UI changes don't show up | preview serves a build, not a live server — re-run bun run build and refresh. |
| "Authentication Error — URL NOT FOUND" in the Shell | VITE_API_URL is missing its trailing /api, so token exchange 404s. Fix ui/.env, then re-build. |
| App mounts but every API call goes nowhere | ui/.env didn't exist when you built. Create it and bun run build again — VITE_* is inlined at build time, not read at startup. |
| App loads but calls fail (CORS) | The app API's APP_ENV_CORS_ORIGINS must include https://veni.dev.venizia.ai. |
| "not signed in" / token rejected | APP_ENV_SHELL_JWKS_URL still points at localhost — set it to the Dev Shell API and restart the API. |
| CLI install fails to download (401/403) | The installer's sign-in didn't complete, or the PAT you passed is invalid — re-run it and approve the code in the Dev Shell. |
veni <cmd> → Not signed in — run veni login first. | No token saved on this machine. veni login --shell https://shell-api.dev.venizia.ai, or set VENI_TOKEN for one run. |
See also
- Get started — the hub: cloud vs local, and the moving parts.
- Everything local — run the whole platform on your machine instead.
- Build a satellite — scaffold and grow your own app.