How it works · the workbench
The workbench
Where you work. One container per person, holding the tooling, with every environment mounted inside it. An environment is the opposite — a runtime and its services, and nothing else — so the two never have to agree about versions of anything, and an environment stays small enough to clone.
If you find yourself wanting to install a language server, a linter, or a CLI, it belongs here.
What is in it
A shell, tmux, git, node, psql, Playwright (it drives the box's browser), the gh command, and Claude Code — signed in to your Claude account, which is what lets the box run sessions from the app. Every env var in the box's sets is in every shell, and in a session's next command the moment it lands. Define GH_TOKEN and a credential helper answers for github.com with it, so git clone and git push over https just work and no URL ever carries it.
There is no web editor and no web terminal, on purpose. You reach the workbench with your own ssh client and VS Code Remote SSH, using a key you added in Settings. Source code and keystrokes go straight to the machine and never through anything of ours.
ssh <box>@home.<handle>.bettik.dev
A box can have more than one workspace, each a workbench of its own (list_workspaces). That login lands in the first, main. Every other workspace has a login of its own at the same address, wksp-<name>: ssh wksp-work@ the address above lands in the workspace work, with the same keys.
Where things are
~/ you start here: /home/<box>, and it is yours
.claude/ your Claude settings, skills and memory
.bettik/ what the box delivers: env vars, as files
scratch/ anything not tied to an environment
public/ shared: anyone with a file's link can open it
environments/<name>/ one directory per environment -- the whole environment
.env what Bettik knows: the address, DATABASE_URL, secrets
projects/<name>/ the repositories. This is where code lives.
compose.yaml, seed, start how the environment runs (see the environment directory)
An environment may hold several repositories under projects/ — a site and an API side by side is normal.
Your home is the box's data disk. It survives everything: restarts, updates, the workbench container being rebuilt. Shell history, dotfiles and ~/.claude come back as they were. The box writes nothing into ~/.claude: what it needs a session to know, it tells the session as it starts, so the directory is yours to keep however you like — a git repository, if you want it to follow you.
~/public is the one folder that is not private. A file in it opens at public.home.<handle>.bettik.dev under the same path, for anyone with the link; only you see the list. See the public folder.
Names on the network
Each environment has its own network, on which its services reach each other by name. The workbench is on every environment's network, so from here every container of every environment is reachable by <environment>-<service> and the port the service itself listens on:
psql postgresql://app:…@catan-db:5432/app # from the workbench
curl http://catan-web:3000 # the app, by name
Nothing here needs a port number. The workbench is workbench to an environment, so something you run here on 0.0.0.0 is reachable from any environment as workbench:<port>.
An environment cannot reach another environment by name: catan does not see todo-db. One environment talks to another the way anyone outside does, at its public address, and gets only what that environment opens — open paths in its bettik.json, or the whole environment shared public — behind whatever token its code checks. There is no private link between environments.
Every lookup, from an environment or from here, goes through the box's own resolver, which counts the names each one asks for. It blocks nothing; it is how the box can say where each environment talks to.
A browser
The box's own browser, Google Chrome, is bettik-browser:3000 from here: a session drives it over CDP, in a context of its own.
const browser = await chromium.connectOverCDP('ws://bettik-browser:3000/cdp/my-test?holder=me');
const page = await browser.contexts()[0].newPage();
Services has the rest: contexts, your own logins, and the live view. An environment may bring its own browser (a browser service in its compose.yaml); a session working on that environment uses it instead.
Entering an environment
A session starts in ~, inside no environment. /bettik-environment <name> puts it in one: it claims the environment, moves to ~/environments/<name>, and reads the environment's .env and CLAUDE.md. From then on it works there as in any repository. /bettik-environment new <name> installs one and does the same; /bettik-environment leave releases the claim and comes back to /work.
You
/bettik-environment catan
Claude
Checks the environment exists and who holds it, claims it to write as this session, moves there, and says the address from .env.
A person at an ssh prompt does the same by hand: cd ~/environments/catan, and claim it if a session might be in there too.
What the tools are for, and what they are not
The tools are for what only the box can do: install an environment, clone it, bounce its containers, read what they printed, save it. Inside an environment you run npm, git and psql yourself, in the workbench; nothing about working needs a tool. A session on the box has the tools pre-approved, except the one that deletes data.
The database, from here
Each environment's .env is the only place its credentials are:
set -a; . ~/environments/catan/.env; set +a
psql "$DATABASE_URL"
The app container gets the same DATABASE_URL in its environment variables. There is no shell inside an environment's containers and no need for one: the code is here, the database is on the network, and what the containers print is environment_logs.