Example organisation
Start a local Chelabase stack with a demo organisation, forty people in five teams, schemas, row-level security, app users and a branch to merge, and what to try in it.
This page starts a local stack with a demo organisation already in place: a project, a database, teams and their access, schemas with row-level security, app users and a branch to merge. It is for trying Chelabase out. For a real instance, see Install.
Local demo only. Every account in the demo uses one published password, Acme-Demo-2026!. Anyone who can reach the stack can sign in as any of them. Run it on your own machine and do not expose it.
The seed refuses to run unless the three hosts are localhost or *.localhost and both the gateway and the Postgres proxy are published only on loopback. It also refuses a stack that requires email verification, and an instance that someone else has already claimed.
Start it
The steps are the ones in the header of infra/compose/docker-compose.example.yml:
cd infra/compose && cp .env.example .env
docker compose -f docker-compose.yml -f docker-compose.example.yml up -d --wait
docker compose -f docker-compose.yml -f docker-compose.example.yml logs example-seedTo run the seed from your checkout's code instead of the published image, build the images first:
docker compose -f docker-compose.yml -f docker-compose.build.yml build init-secrets studio
docker tag chelabase/server:local chelabase/init:local
CHELABASE_IMAGE_REPO=chelabase CHELABASE_IMAGE_TAG=local docker compose \
-f docker-compose.yml -f docker-compose.build.yml -f docker-compose.example.yml up -d --waitThe seed claims the instance for Ada, so you don't use a setup code by hand. Its log prints the account list. Then open http://studio.chelabase.localhost:8000 and sign in as any demo person.
If up stops part way, run it again: the seed picks up where it stopped. Its last step marks the demo complete with a comment on the acme database's main branch. After that, every up skips the seed and logs that it made 0 writes, so whatever you change in the demo stays changed. That includes unprotecting main, renaming the organisation and removing people from a team.
Reset
cd infra/compose
docker compose -f docker-compose.yml -f docker-compose.example.yml down -vThis deletes every database and every secret, the completion mark with them, so the next up seeds a fresh demo. Start again from the top afterwards. Computes are not Compose services, so down -v leaves them behind. For a full removal, follow the steps in Stop, tear down and uninstall.
Who is in it
Everyone signs in with the password Acme-Demo-2026!. Emails are <first name>@acme.example, so Bea is bea@acme.example. There are 40 people: Ada, and 39 others. Ada is the instance's admin.
The team names below are the Studio names. Members are listed by first name.
| Team | Level on project Acme | Acts as (Postgres role) | Members |
|---|---|---|---|
| Engineering | Developer | engineering | ada, grace, katherine, linus, barbara, dennis, ken, margaret, radia, tim, frances, annie |
| Finance | Developer | finance | katherine, rosalind, emmy, joan, luca, blaise, sofia, carl |
| Sales | Developer | sales | carl, bea, cecilia, dorothy, elizabeth, florence, gertrude, hypatia, irene, jocelyn |
| Support | Viewer | support | cecilia, lise, maria, nettie, ynes, valentina, sally, mae, annie, henrietta |
| Leadership | Admin | all roles | ada, hedy, alan, marie, rachel |
A Developer runs SQL as one of the Postgres roles its team is granted. A Viewer can browse but not run SQL. An Admin can act as every role. See Members and invites and Roles and schema isolation.
People can be in more than one team. Carl is in Finance and Sales, so he can act as either role.
One override. alan@acme.example is an Admin through Leadership, but on the acme database he is narrowed to Viewer. The override applies to that database only.
App users. carol@customer.example, dave@customer.example and erin@customer.example are customers. They use the same password, and they sign in through the client, not Studio. See App users.
What is in the database
The project is Acme, and it has one database, acme. Each team works in its own schema, and each schema is owned by its team's Postgres role.
| Schema | Owned by | Tables |
|---|---|---|
engineering | engineering | none |
finance | finance | finance.invoices |
sales | sales | sales.orders |
support | support | support.tickets |
public | public.customers, public.employees |
Row-level security is on for every table except public.employees. That table has none on purpose, so Studio's security warning shows it. See Studio.
public.customers: an app user sees only their own row. Thesales,financeandsupportroles read every customer.sales.orders: an app user sees only their own orders (customer_id = chelabase.user_id()). Sales can read and write every order, and Finance can read them.finance.invoices: only Finance can reach it.support.tickets: an app user sees only their own tickets. Thesupportrole sees the tickets of the EMEA region.public.employees: no row-level security. Anyone with the public key can read every row through the data API. App users can't change it: the seed revokes the data API's write grants on it, and onpublic.customers.
Each team's tables are owned by the team's role, and an owner normally skips its own table's policies. The seed forces row-level security on sales.orders, finance.invoices and support.tickets, so the policies apply to their owners too.
Deleting an app user deletes their row in public.users. Their customer row, orders, invoices and tickets are deleted with it (on delete cascade), so deleting a customer in Studio works.
The data API's default schema is sales, so from("orders") means sales.orders. Other tables need their schema, for example from("public.customers"). See TypeScript client and Data API.
Keys. The main branch of database acme has the public key and a secret server key named acme-server. The seed never prints a secret key. See Keys.
Branches. main is protected. feature-discounts is ready to merge. It adds sales.orders.discount_pct, a percentage from 0 to 100 that defaults to 0. See Branches and Merging schema.
Saved queries. The SQL editor has two, shared across the project: "Revenue by month" and "Open tickets by region". See SQL editor.
Things to try
-
Write across teams. Sign in as
bea@acme.example. In the SQL editor, set Run as tosalesand run:insert into finance.invoices (order_id, customer_id, amount, issued_on) select id, customer_id, total, current_date from sales.orders limit 1;Postgres refuses it with a permission error, because the
salesrole has no rights onfinance. Now run the same kind of insert intosales.orders, for exampleinsert into sales.orders (customer_id, total) select id, 10 from public.customers limit 1;. It works.mainis protected, so Studio asks you to confirm each change before it runs. -
Browse without running SQL. Sign in as
lise@acme.example. She is in Support, a Viewer. She can browse the tables, but she cannot run SQL. -
See Support's region rule. Support members are Viewers, so try it as Ada. Sign in as
ada@acme.example, set Run as tosupportin the SQL editor and runselect subject, region from support.tickets;. Only the EMEA tickets come back. Run as yourself (the owner) and you see every region. -
Find the unprotected table. Open Studio's security warning. It flags
public.employees, which has no row-level security. Anyone with the public key can read it, but not change it. -
Read orders as a customer. Sign in as
carol@customer.examplethrough the client, and readsales.orders. You see only Carol's rows. The client needs the generated types (see Generated types). Replace the key with your public key:import { createClient } from "@chelabase/client"; import { type Database, types } from "./db"; const db = createClient<Database>({ url: "http://api.chelabase.localhost:8000", key: "chk_pub_…", types, }); await db.auth.signIn({ email: "carol@customer.example", password: "Acme-Demo-2026!" }); const orders = await db.from("orders").select("id", "total", "status", "created_at"); -
Merge a branch. Open the
feature-discountsbranch and merge it intomain. The merge addsdiscount_pcttosales.orders. Follow Merging schema for the steps. -
See the override. Sign in as
alan@acme.example. He can read theacmedatabase but cannot change it. See Members and invites.