Everything local
Run the whole platform on your machine: start the Shell and its services, then run (or build) an app against it. Install the prerequisites first.
Two ways to bring up the Shell — the one-command script is the fast path most people want; the manual route is there when you need full control.
Writes the compose, brings up Redis + the Shell's database, and starts the fully-configured Shell (plus Veni builder) — signing keys generated for you. Needs Docker + Bun.
$bash dev-up.shRun each piece yourself and see exactly what happens.
- 1 Clone & start services
- 2 Configure & run
The shape you'll build — the Shell and your app both run on your machine, each with its own data; your app federates into the Shell and verifies its tokens:
1 · Clone the Shell & start its services
The Shell needs three backing services up before it boots — a database (users, orgs), a cache, and object storage.
Clone the Shell into an empty workspace folder and bring up Redis (cache + event bus), MinIO (S3 object storage) and its database — all shipped in the repo's docker-compose.yml. (Keycloak — SSO — is a Shell service too, but locally you sign in with seeded accounts, so skip it.)
git clone https://github.com/VENIZIA-AI/veni-shell.git
cd veni-shell
docker compose up -d redis minio shell-db # redis + minio: services · shell-db: Shell database
docker compose ps # all three should read "healthy" before you run the ShellPorts and credentials are preset to match api/.env, so nothing needs editing. Each app owns its own database (the SCS split) and ships it in its own repo — see Services for the full list.
2 · Configure & run the Shell
A Shell you can open at localhost:5173 and sign into — which means a secret it can sign tokens with, a schema in the database, and an account that exists.
Configure. From the veni-shell folder, copy the env:
cp api/.env.example api/.envapi/.env — the copied defaults are already wired for local (they match the compose): the Shell's own database and Redis, its API port 3000, and UI URL 5173. Every line is documented in .env.example. Two values are yours to set — a signing secret, and an RSA key. Copy the block:
# 1 · signing secret — the Shell won't boot without it
sed -i.bak "s|^APP_ENV_JWT_SECRET=.*|APP_ENV_JWT_SECRET=$(openssl rand -hex 32)|" api/.env && rm -f api/.env.bak
# 2 · RSA key — needed the moment you add an app (step 5)
sed -i.bak '/^APP_ENV_JWT_PRIVATE_KEY=/,/END PRIVATE KEY-----"/d' api/.env && rm -f api/.env.bak
printf 'APP_ENV_JWT_PRIVATE_KEY="%s"\n' \
"$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 | openssl pkcs8 -topk8 -nocrypt | sed ':a;N;$!ba;s/\n/\\n/g')" \
>> api/.envVerify both landed — each should print a value, not a blank:
grep -c '^APP_ENV_JWT_SECRET=.\+' api/.env # → 1
grep -c '^APP_ENV_JWT_PRIVATE_KEY=".\+' api/.env # → 1Why the RSA key, and when you can skip it
Skip it only if you stop at the Shell. Blank, the Shell signs tokens with HS256 and local password login works fine — but a remote app can't verify those tokens, so it fails token exchange with "Shell JWKS returned no usable signing keys" and loads as "not signed in." Since this page goes on to add an app, set it now. (dev-up.sh always does.)
The key is served through the Shell's JWKS endpoint so remote apps can verify Shell-issued tokens with RS256.
To generate one by hand instead — a PKCS#8 RSA-2048 key — and paste the full -----BEGIN PRIVATE KEY----- … -----END PRIVATE KEY----- block into APP_ENV_JWT_PRIVATE_KEY (multi-line is fine, or escape the newlines as \n):
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 | openssl pkcs8 -topk8 -nocryptRestart the Shell API after changing either value.
Run. The Shell's Makefile wraps bun and drives both the API and UI from one terminal:
# from veni-shell/
make install # install deps, api + ui (bun install)
make migrate # apply the database schema
make seed # seed accounts — admin@venizia.local / Admin123!
make dev # start API :3000 + UI :5173 in the background (logs: api.log / ui.log)Or drive bun yourself — the API and the UI are separate processes, so they need a terminal each. Copy one block per terminal:
# Terminal 1 · API :3000 — from veni-shell/
cd api
bun install
bun run db:migrate # apply the database schema
bun run seed # seed accounts — admin@venizia.local / Admin123!
bun run dev# Terminal 2 · UI :5173 — from veni-shell/
cd ui
bun install
bun run devCheck the API booted: curl http://localhost:3000/api/health → {"status":"ok"}.
The Shell is up — sign in
Open http://localhost:5173 and sign in as admin@venizia.local / Admin123! (local password). The full set of seeded accounts — every role, org members, SSO users — is in Run → Sign in.
For SSO (Google / Keycloak / OIDC), see Shell → Configure; the RSA key is in Shell → Run.
Add an app
The Shell's up — now add a satellite. HRM is the example; every app follows the same shape (swap the name, port and DB). Building your own instead? See Build a satellite.
3 · 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. None of HRM's data lives in the Shell's; that's the whole point of a self-contained system.
From your workspace folder (next to veni-shell):
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 repo4 · Configure & run
The app answers on its own port, talks to its own database, and can prove that a token the 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 local dev: this app's own database and Redis, the shared event bus, the Shell's JWKS, and the port you'll register (3012). Every line is documented in .env.example — the one value you set is the signing secret:
sed -i.bak "s|^APP_ENV_JWT_SECRET=.*|APP_ENV_JWT_SECRET=$(openssl rand -hex 32)|" api/.env && rm -f api/.env.bakui/.env — nothing to change: the copied .env.example already points at the local Shell, and Vite loads it for bun run dev. (Deployed Dev/UAT/prod take no build-time env — the UI reads its config at runtime from public/env-config.js, so one build runs everywhere. Full var list: ui/.env.example.)
Run. API and UI are separate processes — a terminal each. Copy one block per terminal:
# Terminal 1 · API :3012 — from hrm/
cd api && bun install && bun run db:migrate && bun run dev# Terminal 2 · UI :4175 — from hrm/
cd ui && bun install && bun run devCheck it booted: curl http://localhost:3012/api/health → {"status":"ok"}.
5 · Register it with the Shell
The app is running, but the Shell has never heard of it. It should appear on the launchpad and load at /developer-hrm — signed in, no second login.
Registering an app is a developer action, so sign in as the seeded developer — developer@venizia.local / Developer123! (the admin account from step 2 has no My Apps):
At http://localhost:5173, flip the sidebar toggle End-user → Developer. You land on My Apps.
Fill the form:
Field Value Local UI URL http://localhost:4175Display name HRMslug hrmClick Register app. A
localhostURL must have the slug — it becomes the path.
Done — HRM is on the launchpad. Open **http://localhost:5173/developer-hrm**: you're already signed in, no second login.
Why /developer-hrm and not /hrm?
Self-service prefixes the slug with your username, so several developers each register their own hrm without colliding, and each copy is a private draft only its owner sees. The unprefixed /hrm is an admin action — see Register.
Prefer the CLI — or registering another app?
CLI — one command from the app root (needs the veni CLI, which the local builder serves; dev-up.sh runs it):
veni register --shell http://localhost:3000 # PAT → self-service: /<you>-hrm, private draft — same as the UI steps above
veni register --shell http://localhost:3000 --user admin # admin login → unprefixed /hrm, for everyoneIt also rewrites api/.env — restart the app's API after. A PAT can't prove you're an admin, so plain veni register always takes the self-service route (why).
Another app — same three steps, different values. Drive: local UI URL http://localhost:4176, slug drive, opens at /developer-drive. Full reference + troubleshooting: Register.
Want more apps? Repeat steps 3–5 for each — they never collide (each owns its own database and ports).
Start over — reset a broken local stack
Two levels, smallest first.
Just the data — the schema drifted or the seeds are wrong, but the containers are fine. From veni-shell/:
make db-reset CONFIRM=yes # drop every table, re-migrate, re-seedEverything — wipe the containers and their volumes (database, cache, object storage), then start clean. Run make stop first if you launched with make dev:
make stop # from veni-shell/ — stop the background API + UI
docker compose down -v # from veni-shell/ — drops shell-db, redis, minio data
docker compose down -v # from hrm/ — drops hrm-db dataA full wipe leaves the databases empty — re-run migrate + seed (steps 2 and 4) before signing in again.
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 that repo's api/.env. |
| Can't sign in | Run make seed; use admin@venizia.local / Admin123!. |
| Shell can't reach its DB | shell-db isn't up, or APP_ENV_DATABASE_URL uses :5432 instead of the mapped host port :15433. |
| App loads but "not signed in" | Shell RSA key unset — set APP_ENV_JWT_PRIVATE_KEY in veni-shell/api/.env (step 2), restart the Shell API. |
| App missing from the launchpad | App must be running; register it (step 5) and reload the Shell. |
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 <app>-db), or APP_ENV_DATABASE_URL uses :5432 instead of the mapped host port. |
veni: command not found | Install the CLI (Veni builder must be running), and put ~/.local/bin on PATH. |
| A service won't start: port is already allocated / in use | Something else holds that port — check Ports at a glance, then stop the other process or remap it. |
Shell UI never goes healthy; ui.log shows does not provide an export named 'styleText' | Node is below 20.12 (or on 21.0–21.6, which misses the backport). Install 22 LTS. The API is fine, so only the UI hangs. Prerequisites |
See also
- Get started — the hub: local vs cloud, and the moving parts.
- Cloud setup — the same app, run against the hosted Dev Shell instead.
- Build a satellite — build your own app end to end.