BettikDocsSign in

How it works · environments

Environments

An environment is a thing that runs: a runtime, the services beside it, and a directory shared between them and the workbench, at its own address. Installing one takes about thirty seconds. Cloning one takes a few. You can have as many running as the box's memory holds, and stopped ones cost only disk.

An environment runs on your box; an environment template — a template, for short — is a frozen copy of one, kept by your account, which you can install again. How environments and templates work has what happens underneath. Every operation below is something you say to Claude; the tools page has each one as Claude sees it.

Installing

You

Install node22-postgres17 as catan.

Claude

Runs list_templates first if you did not say which template, tells you what the environment brings, and asks for a name if you did not give one — because the name becomes the address.

Bettik

Environment "catan" installed from "node22-postgres17". Runtime Node 22 (node:22-trixie-slim), listening on $PORT Services Postgres 17 at <environment>-db:5432, database app, role app projects/ empty -- create a project or clone one in, then start it from start Address https://catan.home.<handle>.bettik.dev will answer once there is a project and start starts it; nothing is running there yet. In the workbench it is ~/environments/catan. compose.yaml is how it is set up, seed and start are how it fills and runs, .env is what Bettik knows, CLAUDE.md is the guide. Claimed for "phone-session" to write. Release it with release_environment when you are done.

You can install any template you can see: the starter kits that come with Bettik, your own templates, those of an organization you belong to (org/name), or a template someone shared with you by link (frank/worbblepop). Add @3 for a particular version; without it you get the newest. If the box does not have that template yet, install fetches it first — there is nothing to download beforehand.

What happens on the box: the environment's directory is copied (a reflink copy — instant, sharing unchanged blocks), the containers are built and started from its compose.yaml, Bettik mints a value for each secret the environment declares and writes .env, and the environment's seed runs once inside the app container. If seed fails, the install fails, with the script's last lines as the reason.

A big template — a saved environment with its data, many gigabytes — takes minutes to fetch and copy. Install does not make you wait on the line for it: it answers once the install is under way, names the box it is on, and environment_status shows the install from that moment, step by step — fetching (waiting for the transfer to start, then arriving), copying, building, seeding, publishing — and says why if one fails. The environment is claimed for the session as soon as it is made, before seed runs. Asking again with the same name and template is the same install: it reports on it, and fetches nothing twice. An install from a session on one box, when your current box is another, is refused rather than made on the other box: switch_box to the box you are on, or name the box you mean.

A starter kit, an environment template of kind kit, is a place to start. A fresh node22-postgres17 has Node and Postgres and an empty projects/; the address answers once there is a project and start starts it. The first thing after installing is usually to put one there.

Naming

The name is the address — <name>.home.<handle>.bettik.dev — which you will type and share for months. Claude asks what to call it and says why. Anything is accepted: it is lowercased and punctuation becomes hyphens, so Catan v2 becomes catan-v2, and if the name had to change to be a hostname, the tool says so and Claude repeats it. Nobody learns their URL by accident.

Cloning

You

Clone catan as catan-v2.

Bettik

Environment "catan-v2" cloned from "catan". Address https://catan-v2.home.<handle>.bettik.dev This is a copy-on-write clone of "catan" -- its database contents, dependencies and build cache came across, and the two diverge from here. Claimed for "phone-session" to write.

A clone is the whole directory: projects/ with everything installed, the build cache, and the live database contents — the rows, not an empty schema. Copy-on-write, so it takes seconds regardless of size and two environments share the disk they have in common until they diverge. The clone's .env names the clone; its address is its own. It works while the original runs.

This is how you get an isolated copy. Not a git worktree: that separates the source and leaves you rebuilding dependencies against a shared database, which is the problem an environment already solves. Want to try something risky? Clone.

What an environment needs from you

Some environments need something only you have: a GitHub token to clone a private repository, an AWS key, a sandbox account. An environment says so in its bettik.json, one line each, and list_templates shows it before you install:

"requires": {
  "GITHUB_TOKEN":      { "description": "to clone the code" },
  "STRIPE_SECRET_KEY": { "description": "Stripe sandbox", "env": true },
  "AWS_ACCESS_KEY_ID": { "description": "for LTB work", "optional": true }
}

Set them once, as env vars on the settings page. When the environment is installed, each is filled from yours and handed to its seed, update and stop -- only the names it asks for, nothing else you have. "env": true puts it in the environment's .env for the app container as well. Change a value and the next build or start picks it up.

If one is missing, the install stops before anything is made and says which, and what it is for. GH_TOKEN and GITHUB_TOKEN answer for each other.

Running, stopping, building, bouncing

Four verbs, each one thing:

stop_environment
The environment's stop script runs if it has one, then the containers come down. Every byte stays, and so does the address; it stops counting against memory.
start_environment
A stopped environment comes back over the same directory, at the same address, without rebuilding anything. Counts against memory again.
build_environment
Apply the directory as it is now: compose.yaml and bettik.json re-read, images built or pulled, only the containers that changed recreated, .env rewritten, newly declared secrets minted. Idempotent — with nothing changed, nothing happens. This is the one after editing the environment.
bounce_environment
Restart the processes — all of them, or one service by name — without touching its setup. For a dev server that did not notice an install, or a wedged process.

And two that run the environment's own scripts: update_environment runs update (pull, reinstall, migrate — whatever the environment says an update is; safe to run twice), and seed_environment runs seed again, from blank, inside the app container — for a seed written after the environment existed, which is what /bettik-try does.

Looking

You

What's running, and what did catan's containers print?

Claude

environment_status for all of them — state, address, memory and CPU, when each was last active, who holds what — then environment_logs for catan.

Sessions run in the workbench, outside the environment, so environment_logs is how they see a build error, a crash loop, or a query the app logged. Read-only; the last hundred lines by default, up to two thousand.

Claiming

Before a session changes anything in an environment it claims it, so a second session sees that and clones rather than colliding. One writer, or many readers; a claim is held until released and does not lapse when a session goes quiet. In parallel is the long version. /bettik-environment <name> claims for you; release_environment with nothing named releases everything a session holds, which is what to do when it finishes.

Saving

When an environment is the way you want it, save it: it becomes the next saved environment of its template, which outlives the box and can be installed again anywhere.

You

Save catan.

One step: the environment stops for a few seconds while its whole directory is captured — projects/ with dependencies installed, the database contents as they are, and the env vars it needs — and starts again. The templates page has the rest.

Removing

destroy_environment removes the environment from the box: its containers and all of its data, including the database. Anything not saved is gone and nothing undoes it. It is the one tool a session on the box is not pre-approved for: it asks every time, naming the environment, unless you have just asked for that specific one to go. It is also the one operation a claim can refuse — an environment another session is in the middle of cannot be removed until they release it.

An environment you might want back is stopped, or saved first. Saving is also how you free the box: save it, remove it, install it again when you need it.

What is where

~/environments/catan/
  bettik.json      the contract: which service is the app and its port, secrets to mint, .env, timeouts
  compose.yaml     its setup -- Docker Compose, yours to edit
  seed             ran once, at install
  update           bring it current
  start            runs every start, in the app container, and stays in the foreground
  stop             optional
  .env             written by Bettik on every start: _BETTIK_ENVIRONMENT, _BETTIK_APP_URL, DATABASE_URL, the secrets
  projects/        the code
  CLAUDE.md        the guide a session reads when it enters
  README-bettik.md what the environment says about itself

The environment directory page has the contract in full: every file, every variable, every script's moment.

Showing it to someone

An environment's address opens for you, signed in, and for nobody else. When you want to hand it to a person with no Bettik account -- a friend, a client, a playtester -- make it public: share_environment with public, or the button beside the environment on the box's Environments tab on the dashboard. Anyone with the URL can then open it, no sign-in, within a minute. environment_status says public beside an environment that is, and the dashboard says so beside the environment, so nothing is public by accident. private takes it back. A clone starts private whatever it was cloned from.

Claude

Asks before making an environment public, since the URL then opens for anyone who has it, and repeats the address once it is.

Public is about the environment's address only. The box, the dashboard and the connector are never public, and the machine still takes requests only through its tunnel. Names and addresses has the rest. Letting someone install their own copy is different: that is sharing a template.

Leaving a path open

Sometimes one path has to be reachable and nothing else: a webhook that Stripe, Slack or GitHub calls. Leave the environment private and name the path in bettik.json, beside its address:

"app":  { "service": "app", "port": 3000, "open": ["/webhook", "/hooks/*"] },
"urls": { "api": { "service": "api", "port": 8080, "open": ["/v1/events"] } }

/webhook opens exactly that path; /hooks/* opens /hooks and everything below it. Nothing else changes: the rest of the environment still needs your sign-in. It takes effect within a beat of the box noticing.

More than one address

An environment answers on its own hostname and on any it adds. An entry in bettik.json's urls — "api": { "service": "api", "port": 8080 } — published in compose.yaml on ${_BETTIK_PORT_API}, then build_environment, and the environment also answers at api-<name>.home.<handle>.bettik.dev. A name in ports is a host port with no hostname, for a debugger or an ssh tunnel. The database is never published to the internet; from a laptop it is a tunnel to that port.

A widget on its card

An environment can put a small view of itself on its card on the box's Environments tab: a dot per service, a count, a button that restarts something. Name it in bettik.json:

"widget": { "url": "monitor", "path": "/widget.html", "height": 64 }

url is which of its addresses serves it (leave it out for the environment's own), path is the page there (/widget.html if left out), height the frame's starting height in pixels, 24 to 320; it then grows or shrinks to fit what the page draws, up to 320, so a widget that wraps on a phone is not cut off. The box fetches the page over loopback while someone has the card open, about every 30 seconds, and the card's open link goes to that address.

What it can do:

What it cannot: it runs in a sandboxed frame with no origin of its own, so it cannot read the page, your cookies or anything else of Bettik's, and it has no network but bettik.call. Nothing outside the environment is reachable from it. The frame is labelled with the environment's name, because what is in it is the environment talking.

Why “environment”

We started with stack. It is accurate — a runtime, a database, a directory Docker runs — but it is a builder's word, and it says nothing about why you would want one.

We thought about calling them apps. For Worbble Pop or 2048 that fits. But plenty of environments are not apps: a copy of Bettik's own repository holding one change, an environment made to try a library, a kit for building games. Whether something is an app depends on how people use it, and only its author knows that.

So Bettik says environment, and authors describe theirs however they like: a game, an app, a kit, a starter.

For a while the kept copies lived in a store. It read as buying and selling, and nothing is sold. Then they were packages, and an environment was a bundle: words for how the thing is made rather than what it is for. Now an environment is the thing that runs, and an environment template is what you make more of them from.

Old names

An environment used to be called a bundle, an environment template a package, a version of a template a release, and a saved environment a snapshot. The old names keep working for a while: the old tool names for one to two weeks after the switch, and ~/bundles and _BETTIK_BUNDLE until every box has stopped using them. If one name is not there, try the other.

The tools:

install_bundle
install_environment
clone_bundle
clone_environment
seed_bundle
seed_environment
update_bundle
update_environment
bounce_bundle
bounce_environment
build_bundle
build_environment
stop_bundle
stop_environment
start_bundle
start_environment
share_bundle
share_environment
destroy_bundle
destroy_environment
claim_bundle
claim_environment
release_bundle
release_environment
transfer_bundle
transfer_environment
bundle_status
environment_status
bundle_logs
environment_logs
list_packages
list_templates
fetch_package
fetch_template
delete_package
delete_template
snapshot_bundle
save_environment
release_snapshot
publish_template
release_status
publish_status

The box's own tools that only the connector calls were renamed too (list_cached_packages, capture_bundle, delete_cached_package are list_cached_templates, capture_environment, delete_cached_template); a session never sees them.

Their arguments, and on the box:

bundle_id
environment_id
package
template
snapshot
saved
~/bundles
~/environments
_BETTIK_BUNDLE
_BETTIK_ENVIRONMENT
/mcp__bettik__bundle
/bettik-environment
/mcp__bettik__environment
/bettik-environment
/mcp__bettik__try
/bettik-try

The bettik://bundles and bettik://environments resources, and each environment's .env offered as one, are gone with the box's MCP server: ls ~/environments and the .env files themselves say the same.

These pages, whose old addresses lead to the new ones:

/docs/bundles
/docs/environments
/docs/how-bundles-work
/docs/how-environments-work
/docs/bundle-directory
/docs/environment-directory
/docs/packages
/docs/templates
/docs/on-the-box/bundles
/docs/on-the-box/environments