The booth wrapper script is location-based: it operates relative to its own location, not the current working directory. This means:
- Running
./boothfrom the project root works as expected - Running
/path/to/project/boothfrom any directory also works correctly - The script always finds
.booth/in the same directory whereboothis located
- The launcher passes your host UID and GID into the container using the environment variables
HOST_UIDandHOST_GID. - Inside the container, the entrypoint script (
booth-entry) ensures a matchingcoderuser and group exist with those IDs. This needs rootful Docker on Linux (or Docker Desktop on macOS/Windows). Linux rootless Docker and userns-remap map the host user to root, so a separatecodercannot be created —boothrefuses those modes unless--rootlessis passed. - The directories
/home/coderand/home/coder/codeare owned by that user, ensuring smooth file sharing between host and container. - Add the user
coderto sudoers so that it can sudo without needing the password - Prepare
.bashrcand.zshrc - Run startup scripts (system scripts from
/usr/share/startup.d/, then user scripts from.booth/startups/) - All commands run as the unprivileged
coderuser, notroot, preserving security and consistent file ownership.
On Windows hosts, a bind-mounted file carries no Unix owner of its own, so Docker Desktop presents it as
root:rootand step 3 above reconciles it. That reconciliation cannot reach.booth/or theboothwrapper, because both are mounted read-only (see The.booth/Folder) — achownis a metadata write, which a read-only mount rejects even forroot. Insteadbooth configandbooth initstamp the ownership host-side, once, at the moment they write.booth/. This works because Docker Desktop reaches a Windows drive through WSLdrvfsmounted withmetadata, which stores thechownas an NTFS extended attribute on the host file — a property of the file, not of the mount, so every later read-only mount reads it back. The result iscoder-owned and still read-only.One consequence: if you drop the
boothwrapper into a project after configuring it, it keeps reading back asrootuntil the nextbooth config. It stays readable and executable either way, so this is cosmetic.
host # your machine
├── ~/.cache/codingbooth/ # shared binary cache
| └── versions/
| └── <version>/ # version-specific binary
| ├── codingbooth.sha256
| └── codingbooth-<os>-<arch> # binary for current platform
├── project/ # your project folder on the host
| ├── booth # booth wrapper script
| ├── .booth # booth internal folder
| | └── tools/
| | └── codingbooth.lock # version reference
| ├── ... # other project files
...
container
├── home/
| ├── coder/
| | ├── code/ # your project folder inside the container
| | | ├── booth # booth wrapper script
| | | ├── .booth # booth internal folder
| | | | └── tools/
| | | | └── codingbooth.lock # version reference
| | ├── ... # other project files
| ├── ... # other home files
├── etc/
| ├── profile.d/ # profile script folder
├── opt/
| ├── codingbooth/
| | ├── setups/ # setup script folder
| | | ├── ... # setup scripts
├── usr/
| ├── local/
| | ├── bin/ # program file folder
| ├── share/
| | ├── startup.d/ # startup script folder
...
🧠 In short: CodingBooth mirrors your host identity inside the container — you work as yourself, not as root.
Result: seamless dev environment, no permission headaches.
Understanding what persists across container restarts is critical:
| Location | Persists? | Notes |
|---|---|---|
/home/coder/code/ |
Yes | Bind-mounted from host; this is your project folder |
/home/coder/ (outside code/) |
Partial | Ephemeral by default; use .booth/cache/ to persist specific files |
/opt/, /usr/, /etc/ |
No | System directories; lost on restart |
| Installed packages | No | Must be in Dockerfile to persist |
What this means:
- Your code is safe — it lives on the host and is never lost
- Home directory customizations — use
.booth/home/or.booth/home-seed/to persist dotfiles (see Home Directory Guide) - Home directory state — use
.booth/cache/to persist shell history, tool configs, and other runtime state across sessions (see Local Cache Guide) - Team-shared live state — use
.booth/shared/for git-friendly bind mounts (bookmarks, editor settings, DB connections) (see Shared State Guide) - Installed tools — add them to your
.booth/Dockerfile(orBoothfile) so they're rebuilt each time - Container state — treat containers as disposable; rebuild rather than modify
At container startup, booth-entry populates the home directory from four sources in order:
.booth/home-seed/— Team defaults (no-clobber: won't overwrite existing files).booth/home/— Team overrides (will overwrite existing files)/etc/cb-home-seed/— Host personal defaults (no-clobber)/etc/cb-home/— Host personal overrides (will overwrite)
Each source supports fine-grained control with the .mount-this marker. A directory containing .mount-this is copied as a unit; directories without it copy only individual files and recurse into subdirectories. This allows precise control — for example, seeding only .credentials.json from ~/.claude/ without overwriting other cached settings.
See the Home Directory Guide for details and examples.
💡 Tip: If you need to persist something outside
/home/coder/code/, use.booth/cache/, add it to your Boothfile/Dockerfile, or mount an additional volume viarun-argsinconfig.toml.
📝 Technical Note: CodingBooth uses the Docker CLI (
dockercommand) rather than Docker client libraries. This keeps the codebase simple, portable, and easier to maintain while ensuring compatibility across platforms.
Every CodingBooth container includes documentation and resources at /opt/codingbooth/:
/opt/codingbooth/
├── README.md # This documentation
├── LICENSE # Apache 2.0 License
├── version.txt # Current CodingBooth version
├── AGENT.md # Instructions for AI agents
├── variants/ # Dockerfiles for all variants
│ ├── base/Dockerfile
│ ├── codeserver/Dockerfile
│ └── ...
└── setups/ # Built-in setup scripts
├── python--setup.sh
├── nodejs--setup.sh
└── ...
Run booth--info inside the container to see a quick overview of your environment.